Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Bridge\Symfony\Bundle;
use Sonata\Exporter\Bridge\Symfony\DependencyInjection\Compiler\ExporterCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class SonataExporterBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new ExporterCompilerPass());
}
/**
* {@inheritdoc}
*/
protected function getContainerExtensionClass()
{
return 'Exporter\Bridge\Symfony\DependencyInjection\SonataExporterExtension';
}
}
class_exists(\Exporter\Bridge\Symfony\Bundle\SonataExporterBundle::class);

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Bridge\Symfony\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
final class ExporterCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has('sonata.exporter.exporter')) {
return;
}
$definition = $container->findDefinition('sonata.exporter.exporter');
$writers = $container->findTaggedServiceIds('sonata.exporter.writer');
foreach (array_keys($writers) as $id) {
$definition->addMethodCall('addWriter', [new Reference($id)]);
}
}
}
class_exists(\Exporter\Bridge\Symfony\DependencyInjection\Compiler\ExporterCompilerPass::class);

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Bridge\Symfony\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
final class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('sonata_exporter');
$rootNode
->children()
->arrayNode('exporter')
->addDefaultsIfNotSet()
->children()
->arrayNode('default_writers')
->defaultValue(['csv', 'json', 'xls', 'xml'])
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('writers')
->addDefaultsIfNotSet()
->children()
->arrayNode('csv')
->addDefaultsIfNotSet()
->children()
->scalarNode('filename')
->defaultValue('php://output')
->info('path to the output file')
->end()
->scalarNode('delimiter')
->defaultValue(',')
->info('delimits csv values')
->end()
->scalarNode('enclosure')
->defaultValue('"')
->info('will be used when a value contains the delimiter')
->end()
->scalarNode('escape')
->defaultValue('\\')
->info('will be used when a value contains the enclosure')
->end()
->booleanNode('show_headers')
->defaultValue(true)
->info('add column names as the first line')
->end()
->booleanNode('with_bom')
->defaultValue(false)
->info('include the byte order mark')
->end()
->end()
->end()
->arrayNode('json')
->addDefaultsIfNotSet()
->children()
->scalarNode('filename')
->defaultValue('php://output')
->info('path to the output file')
->end()
->end()
->end()
->arrayNode('xls')
->addDefaultsIfNotSet()
->children()
->scalarNode('filename')
->defaultValue('php://output')
->info('path to the output file')
->end()
->booleanNode('show_headers')
->defaultValue(true)
->info('add column names as the first line')
->end()
->end()
->end()
->arrayNode('xml')
->addDefaultsIfNotSet()
->children()
->scalarNode('filename')
->defaultValue('php://output')
->info('path to the output file')
->end()
->booleanNode('show_headers')
->defaultValue(true)
->info('add column names as the first line')
->end()
->scalarNode('main_element')
->defaultValue('datas')
->info('name of the wrapping element')
->end()
->scalarNode('child_element')
->defaultValue('data')
->info('name of elements corresponding to rows')
->end()
->end()
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}
class_exists(\Exporter\Bridge\Symfony\DependencyInjection\Configuration::class);

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Bridge\Symfony\DependencyInjection;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
final class SonataExporterExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$processor = new Processor();
$configuration = new Configuration();
$config = $processor->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
$this->configureExporter($container, $config['exporter']);
$this->configureWriters($container, $config['writers']);
}
private function configureExporter(ContainerBuilder $container, array $config)
{
foreach (['csv', 'json', 'xls', 'xml'] as $format) {
if (\in_array($format, $config['default_writers'])) {
$container->getDefinition('sonata.exporter.writer.'.$format)->addTag(
'sonata.exporter.writer'
);
}
}
}
private function configureWriters(ContainerBuilder $container, array $config)
{
foreach ($config as $format => $settings) {
foreach ($settings as $key => $value) {
$container->setParameter(sprintf(
'sonata.exporter.writer.%s.%s',
$format,
$key
), $value);
}
}
}
}
class_exists(\Exporter\Bridge\Symfony\DependencyInjection\SonataExporterExtension::class);

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sonata.exporter.writer.csv" class="Exporter\Writer\CsvWriter" public="false">
<argument>%sonata.exporter.writer.csv.filename%</argument>
<argument>%sonata.exporter.writer.csv.delimiter%</argument>
<argument>%sonata.exporter.writer.csv.enclosure%</argument>
<argument>%sonata.exporter.writer.csv.escape%</argument>
<argument>%sonata.exporter.writer.csv.show_headers%</argument>
<argument>%sonata.exporter.writer.csv.with_bom%</argument>
</service>
<service id="sonata.exporter.writer.json" class="Exporter\Writer\JsonWriter" public="false">
<argument>%sonata.exporter.writer.json.filename%</argument>
</service>
<service id="sonata.exporter.writer.xls" class="Exporter\Writer\XlsWriter" public="false">
<argument>%sonata.exporter.writer.xls.filename%</argument>
<argument>%sonata.exporter.writer.xls.show_headers%</argument>
</service>
<service id="sonata.exporter.writer.xml" class="Exporter\Writer\XmlWriter" public="false">
<argument>%sonata.exporter.writer.xml.filename%</argument>
<argument>%sonata.exporter.writer.xml.main_element%</argument>
<argument>%sonata.exporter.writer.xml.child_element%</argument>
</service>
<service id="sonata.exporter.exporter" class="Exporter\Exporter" public="true"/>
<service id="Exporter\Exporter" alias="sonata.exporter.exporter"/>
</services>
</container>

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Exception;
class InvalidDataFormatException extends \RuntimeException
{
}
class_exists(\Exporter\Exception\InvalidDataFormatException::class);

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Exception;
class InvalidMethodCallException extends \RuntimeException
{
}
class_exists(\Exporter\Exception\InvalidMethodCallException::class);

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter;
use Sonata\Exporter\Source\SourceIteratorInterface;
use Sonata\Exporter\Writer\TypedWriterInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
final class Exporter
{
/**
* @var TypedWriterInterface[]
*/
private $writers;
/**
* @param TypedWriterInterface[] $writers an array of allowed typed writers, indexed by format name
*/
public function __construct(array $writers = [])
{
$this->writers = [];
foreach ($writers as $writer) {
$this->addWriter($writer);
}
}
/**
* @param string $format
* @param string $filename
* @param SourceIteratorInterface $source
*
* @throws \RuntimeException
*
* @return StreamedResponse
*/
public function getResponse($format, $filename, SourceIteratorInterface $source)
{
if (!\array_key_exists($format, $this->writers)) {
throw new \RuntimeException(sprintf(
'Invalid "%s" format, supported formats are : "%s"',
$format,
implode(', ', array_keys($this->writers))
));
}
$writer = $this->writers[$format];
$callback = function () use ($source, $writer) {
$handler = \Exporter\Handler::create($source, $writer);
$handler->export();
};
$headers = [
'Content-Disposition' => sprintf('attachment; filename="%s"', $filename),
];
$headers['Content-Type'] = $writer->getDefaultMimeType();
return new StreamedResponse($callback, 200, $headers);
}
/**
* Returns a simple array of export formats.
*
* @return string[] writer formats as returned by the TypedWriterInterface::getFormat() method
*/
public function getAvailableFormats()
{
return array_keys($this->writers);
}
/**
* The main benefit of this method is the type hinting.
*
* @param TypedWriterInterface $writer a possible writer for exporting data
*/
public function addWriter(TypedWriterInterface $writer)
{
$this->writers[$writer->getFormat()] = $writer;
}
}
class_exists(\Exporter\Exporter::class);

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter;
use Sonata\Exporter\Source\SourceIteratorInterface;
use Sonata\Exporter\Writer\WriterInterface;
class Handler
{
/**
* @var SourceIteratorInterface
*/
protected $source;
/**
* @var WriterInterface
*/
protected $writer;
/**
* @param SourceIteratorInterface $source
* @param WriterInterface $writer
*/
public function __construct(SourceIteratorInterface $source, WriterInterface $writer)
{
$this->source = $source;
$this->writer = $writer;
}
public function export()
{
$this->writer->open();
foreach ($this->source as $data) {
$this->writer->write($data);
}
$this->writer->close();
}
/**
* @param SourceIteratorInterface $source
* @param WriterInterface $writer
*
* @return Handler
*/
public static function create(SourceIteratorInterface $source, WriterInterface $writer)
{
return new self($source, $writer);
}
}
class_exists(\Exporter\Handler::class);

View File

@@ -0,0 +1,221 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
/**
* Read data from a Xml file.
*
* @author Vincent Touzet <vincent.touzet@gmail.com>
*/
abstract class AbstractXmlSourceIterator implements SourceIteratorInterface
{
/**
* @var string
*/
protected $filename = null;
/**
* @var resource
*/
protected $file = null;
/**
* @var bool|null
*/
protected $hasHeaders = null;
/**
* @var string[]
*/
protected $columns = [];
/**
* @var resource
*/
protected $parser = null;
/**
* @var int
*/
protected $currentRowIndex = 0;
/**
* @var int
*/
protected $currentColumnIndex = 0;
/**
* @var mixed
*/
protected $currentRow = null;
/**
* @var array
*/
protected $bufferedRow = [];
/**
* @var bool
*/
protected $currentRowEnded = false;
/**
* @var int
*/
protected $position = 0;
/**
* @param string $filename
* @param bool $hasHeaders
*/
public function __construct($filename, $hasHeaders = true)
{
$this->filename = $filename;
$this->hasHeaders = $hasHeaders;
}
/**
* Start element handler.
*
* @param resource $parser
* @param string $name
* @param array $attributes
*/
abstract public function tagStart($parser, $name, $attributes = []);
/**
* End element handler.
*
* @param resource $parser
* @param string $name
*/
abstract public function tagEnd($parser, $name);
/**
* Tag content handler.
*
* @param resource $parser
* @param string $data
*/
abstract public function tagContent($parser, $data);
/**
* {@inheritdoc}
*/
public function current()
{
return $this->currentRow;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->parseRow();
$this->prepareCurrentRow();
++$this->position;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->parser = xml_parser_create();
xml_set_object($this->parser, $this);
xml_set_element_handler($this->parser, 'tagStart', 'tagEnd');
xml_set_character_data_handler($this->parser, 'tagContent');
xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 0);
$this->file = fopen($this->filename, 'r');
$this->bufferedRow = [];
$this->currentRowIndex = 0;
$this->currentColumnIndex = 0;
$this->position = 0;
$this->parseRow();
if ($this->hasHeaders) {
$this->columns = array_shift($this->bufferedRow);
$this->parseRow();
}
$this->prepareCurrentRow();
}
/**
* {@inheritdoc}
*/
public function valid()
{
if (!\is_array($this->currentRow)) {
xml_parser_free($this->parser);
fclose($this->file);
return false;
}
return true;
}
/**
* Parse until </Row> reached.
*/
protected function parseRow()
{
// only parse the next row if only one in buffer
if (\count($this->bufferedRow) > 1) {
return;
}
if (feof($this->file)) {
$this->currentRow = null;
return;
}
$this->currentRowEnded = false;
// read file until row is ended
while (!$this->currentRowEnded && !feof($this->file)) {
$data = fread($this->file, 1024);
xml_parse($this->parser, $data);
}
}
/**
* Prepare the row to return.
*/
protected function prepareCurrentRow()
{
$this->currentRow = array_shift($this->bufferedRow);
if (\is_array($this->currentRow)) {
$datas = [];
foreach ($this->currentRow as $key => $value) {
if ($this->hasHeaders) {
$datas[$this->columns[$key]] = html_entity_decode($value);
} else {
$datas[$key] = html_entity_decode($value);
}
}
$this->currentRow = $datas;
}
}
}
class_exists(\Exporter\Source\AbstractXmlSourceIterator::class);

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
class ArraySourceIterator extends IteratorSourceIterator
{
/**
* @param array $data
*/
public function __construct(array $data)
{
parent::__construct(new \ArrayIterator($data));
}
}
class_exists(\Exporter\Source\ArraySourceIterator::class);

View File

@@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use ArrayIterator;
class ChainSourceIterator implements SourceIteratorInterface
{
/**
* @var ArrayIterator
*/
protected $sources;
/**
* @param array $sources
*/
public function __construct(array $sources = [])
{
$this->sources = new ArrayIterator();
foreach ($sources as $source) {
$this->addSource($source);
}
}
/**
* @param SourceIteratorInterface $source
*/
public function addSource(SourceIteratorInterface $source)
{
$this->sources->append($source);
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->sources->current()->current();
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->sources->current()->next();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->sources->current()->key();
}
/**
* {@inheritdoc}
*/
public function valid()
{
while (!$this->sources->current()->valid()) {
$this->sources->next();
if (!$this->sources->valid()) {
return false;
}
$this->sources->current()->rewind();
}
return true;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if ($this->sources->current()) {
$this->sources->current()->rewind();
}
}
}
class_exists(\Exporter\Source\ChainSourceIterator::class);

View File

@@ -0,0 +1,159 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
/**
* Read data from a csv file.
*
* @author Vincent Touzet <vincent.touzet@gmail.com>
*/
class CsvSourceIterator implements SourceIteratorInterface
{
/**
* @var string
*/
protected $filename = null;
/**
* @var resource
*/
protected $file = null;
/**
* @var string|null
*/
protected $delimiter = null;
/**
* @var string|null
*/
protected $enclosure = null;
/**
* @var string|null
*/
protected $escape = null;
/**
* @var bool|null
*/
protected $hasHeaders = null;
/**
* @var array
*/
protected $lines = [];
/**
* @var array
*/
protected $columns = [];
/**
* @var int
*/
protected $position = 0;
/**
* @var array
*/
protected $currentLine = [];
/**
* @param string $filename
* @param string $delimiter
* @param string $enclosure
* @param string $escape
* @param bool $hasHeaders
*/
public function __construct($filename, $delimiter = ',', $enclosure = '"', $escape = '\\', $hasHeaders = true)
{
$this->filename = $filename;
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
$this->escape = $escape;
$this->hasHeaders = $hasHeaders;
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->currentLine;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function next()
{
$line = fgetcsv($this->file, 0, $this->delimiter, $this->enclosure, $this->escape);
$this->currentLine = $line;
++$this->position;
if ($this->hasHeaders && \is_array($line)) {
$data = [];
foreach ($line as $key => $value) {
$data[$this->columns[$key]] = $value;
}
$this->currentLine = $data;
}
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->file = fopen($this->filename, 'r');
$this->position = 0;
$line = fgetcsv($this->file, 0, $this->delimiter, $this->enclosure, $this->escape);
if ($this->hasHeaders) {
$this->columns = $line;
$line = fgetcsv($this->file, 0, $this->delimiter, $this->enclosure, $this->escape);
}
$this->currentLine = $line;
if ($this->hasHeaders && \is_array($line)) {
$data = [];
foreach ($line as $key => $value) {
$data[$this->columns[$key]] = $value;
}
$this->currentLine = $data;
}
}
/**
* {@inheritdoc}
*/
public function valid()
{
if (!\is_array($this->currentLine)) {
if (\is_resource($this->file)) {
fclose($this->file);
}
return false;
}
return true;
}
}
class_exists(\Exporter\Source\CsvSourceIterator::class);

View File

@@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Statement;
use Sonata\Exporter\Exception\InvalidMethodCallException;
class DoctrineDBALConnectionSourceIterator implements SourceIteratorInterface
{
/**
* @var Connection
*/
protected $connection;
/**
* @var string
*/
protected $query;
/**
* @var array
*/
protected $parameters;
/**
* @var mixed
*/
protected $current;
/**
* @var int
*/
protected $position;
/**
* @var Statement
*/
protected $statement;
/**
* @param Connection $connection
* @param $query
* @param array $parameters
*/
public function __construct(Connection $connection, $query, array $parameters = [])
{
$this->connection = $connection;
$this->query = $query;
$this->parameters = $parameters;
$this->position = 0;
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->current;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->current = $this->statement->fetch(\PDO::FETCH_ASSOC);
++$this->position;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function valid()
{
return \is_array($this->current);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if ($this->statement) {
throw new InvalidMethodCallException('Cannot rewind a PDOStatement');
}
$this->statement = $this->connection->prepare($this->query);
$this->statement->execute($this->parameters);
$this->next();
}
}
class_exists(\Exporter\Source\DoctrineDBALConnectionSourceIterator::class);

View File

@@ -0,0 +1,160 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use Doctrine\ODM\MongoDB\Query\Query;
use Doctrine\ORM\Internal\Hydration\IterableResult;
use Sonata\Exporter\Exception\InvalidMethodCallException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyPath;
class DoctrineODMQuerySourceIterator implements SourceIteratorInterface
{
/**
* @var Query
*/
protected $query;
/**
* @var IterableResult
*/
protected $iterator;
/**
* @var array
*/
protected $propertyPaths;
/**
* @var PropertyAccess
*/
protected $propertyAccessor;
/**
* @var string default DateTime format
*/
protected $dateTimeFormat;
/**
* @param Query $query The Doctrine Query
* @param array $fields Fields to export
* @param string $dateTimeFormat
*/
public function __construct(Query $query, array $fields, $dateTimeFormat = 'r')
{
$this->query = clone $query;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
$this->propertyPaths = [];
foreach ($fields as $name => $field) {
if (\is_string($name) && \is_string($field)) {
$this->propertyPaths[$name] = new PropertyPath($field);
} else {
$this->propertyPaths[$field] = new PropertyPath($field);
}
}
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* {@inheritdoc}
*/
public function current()
{
$current = $this->iterator->current();
$data = [];
foreach ($this->propertyPaths as $name => $propertyPath) {
$data[$name] = $this->getValue($this->propertyAccessor->getValue($current, $propertyPath));
}
$this->query->getDocumentManager()->getUnitOfWork()->detach($current);
return $data;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->iterator->next();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->iterator->key();
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->iterator->valid();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if ($this->iterator) {
throw new InvalidMethodCallException('Cannot rewind a Doctrine\ODM\Query');
}
$this->iterator = $this->query->iterate();
$this->iterator->rewind();
}
/**
* @param string $dateTimeFormat
*/
public function setDateTimeFormat($dateTimeFormat)
{
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* @return string
*/
public function getDateTimeFormat()
{
return $this->dateTimeFormat;
}
/**
* @param $value
*
* @return string|null
*/
protected function getValue($value)
{
if (\is_array($value) || $value instanceof \Traversable) {
$value = null;
} elseif ($value instanceof \DateTimeInterface) {
$value = $value->format($this->dateTimeFormat);
} elseif (\is_object($value)) {
$value = (string) $value;
}
return $value;
}
}
class_exists(\Exporter\Source\DoctrineODMQuerySourceIterator::class);

View File

@@ -0,0 +1,210 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use Doctrine\ORM\Query;
use Sonata\Exporter\Exception\InvalidMethodCallException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyPath;
class DoctrineORMQuerySourceIterator implements SourceIteratorInterface
{
const DATE_PARTS = [
'y' => 'Y',
'm' => 'M',
'd' => 'D',
];
const TIME_PARTS = [
'h' => 'H',
'i' => 'M',
's' => 'S',
];
/**
* @var \Doctrine\ORM\Query
*/
protected $query;
/**
* @var \Doctrine\ORM\Internal\Hydration\IterableResult
*/
protected $iterator;
/**
* @var array
*/
protected $propertyPaths;
/**
* @var PropertyAccessor
*/
protected $propertyAccessor;
/**
* @var string default DateTime format
*/
protected $dateTimeFormat;
/**
* @param \Doctrine\ORM\Query $query The Doctrine Query
* @param array $fields Fields to export
* @param string $dateTimeFormat
*/
public function __construct(Query $query, array $fields, $dateTimeFormat = 'r')
{
$this->query = clone $query;
$this->query->setParameters($query->getParameters());
foreach ($query->getHints() as $name => $value) {
$this->query->setHint($name, $value);
}
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
$this->propertyPaths = [];
foreach ($fields as $name => $field) {
if (\is_string($name) && \is_string($field)) {
$this->propertyPaths[$name] = new PropertyPath($field);
} else {
$this->propertyPaths[$field] = new PropertyPath($field);
}
}
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* {@inheritdoc}
*/
public function current()
{
$current = $this->iterator->current();
$data = [];
foreach ($this->propertyPaths as $name => $propertyPath) {
try {
$data[$name] = $this->getValue($this->propertyAccessor->getValue($current[0], $propertyPath));
} catch (UnexpectedTypeException $e) {
//non existent object in path will be ignored
$data[$name] = null;
}
}
$this->query->getEntityManager()->getUnitOfWork()->detach($current[0]);
return $data;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->iterator->next();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->iterator->key();
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->iterator->valid();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if ($this->iterator) {
throw new InvalidMethodCallException('Cannot rewind a Doctrine\ORM\Query');
}
$this->iterator = $this->query->iterate();
$this->iterator->rewind();
}
/**
* @param string $dateTimeFormat
*/
public function setDateTimeFormat($dateTimeFormat)
{
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* @return string
*/
public function getDateTimeFormat()
{
return $this->dateTimeFormat;
}
/**
* @param \DateInterval $interval
*
* @return string An ISO8601 duration
*/
public function getDuration(\DateInterval $interval)
{
$datePart = '';
foreach (self::DATE_PARTS as $datePartAttribute => $datePartAttributeString) {
if ($interval->$datePartAttribute !== 0) {
$datePart .= $interval->$datePartAttribute.$datePartAttributeString;
}
}
$timePart = '';
foreach (self::TIME_PARTS as $timePartAttribute => $timePartAttributeString) {
if ($interval->$timePartAttribute !== 0) {
$timePart .= $interval->$timePartAttribute.$timePartAttributeString;
}
}
if ('' === $datePart && '' === $timePart) {
return 'P0Y';
}
return 'P'.$datePart.('' !== $timePart ? 'T'.$timePart : '');
}
/**
* @param $value
*
* @return string|null
*/
protected function getValue($value)
{
if (\is_array($value) || $value instanceof \Traversable) {
$value = null;
} elseif ($value instanceof \DateTimeInterface) {
$value = $value->format($this->dateTimeFormat);
} elseif ($value instanceof \DateInterval) {
$value = $this->getDuration($value);
} elseif (\is_object($value)) {
$value = (string) $value;
}
return $value;
}
}
class_exists(\Exporter\Source\DoctrineORMQuerySourceIterator::class);

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
/**
* IteratorCallbackSource is IteratorSource with callback executed each row.
*
* @author Florent Denis <fdenis@ekino.com>
*/
class IteratorCallbackSourceIterator extends IteratorSourceIterator
{
/**
* @var \Closure
*/
protected $transformer;
/**
* @param \Iterator $iterator Iterator with string array elements
* @param \Closure $transformer Altering a data row
*/
public function __construct(\Iterator $iterator, \Closure $transformer)
{
parent::__construct($iterator);
$this->transformer = $transformer;
}
/**
* {@inheritdoc}
*/
public function current()
{
return \call_user_func($this->transformer, $this->iterator->current());
}
}
class_exists(\Exporter\Source\IteratorCallbackSourceIterator::class);

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
/**
* SourceIterator implementation based on Iterator.
*/
class IteratorSourceIterator implements SourceIteratorInterface
{
/**
* @var \Iterator
*/
protected $iterator;
/**
* @param \Iterator $iterator Iterator with string array elements
*/
public function __construct(\Iterator $iterator)
{
$this->iterator = $iterator;
}
/**
* @return \Iterator
*/
public function getIterator()
{
return $this->iterator;
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->iterator->current();
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->iterator->next();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->iterator->key();
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->iterator->valid();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->iterator->rewind();
}
}
class_exists(\Exporter\Source\IteratorSourceIterator::class);

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use Sonata\Exporter\Exception\InvalidMethodCallException;
class PDOStatementSourceIterator implements SourceIteratorInterface
{
/**
* @var \PDOStatement
*/
protected $statement;
/**
* @var mixed
*/
protected $current;
/**
* @var int
*/
protected $position;
/**
* @var bool
*/
protected $rewinded;
/**
* @param \PDOStatement $statement
*/
public function __construct(\PDOStatement $statement)
{
$this->statement = $statement;
$this->position = 0;
$this->rewinded = false;
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->current;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->current = $this->statement->fetch(\PDO::FETCH_ASSOC);
++$this->position;
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->position;
}
/**
* {@inheritdoc}
*/
public function valid()
{
return \is_array($this->current);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if ($this->rewinded) {
throw new InvalidMethodCallException('Cannot rewind a PDOStatement');
}
$this->current = $this->statement->fetch(\PDO::FETCH_ASSOC);
$this->rewinded = true;
}
}
class_exists(\Exporter\Source\PDOStatementSourceIterator::class);

View File

@@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use PropelCollection;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyAccess\PropertyPath;
/**
* Read data from a PropelCollection.
*
* @author Kévin Gomez <contact@kevingomez.fr>
*/
class PropelCollectionSourceIterator implements SourceIteratorInterface
{
/**
* @var \PropelCollection
*/
protected $collection;
/**
* @var \ArrayIterator
*/
protected $iterator;
/**
* @var array
*/
protected $propertyPaths;
/**
* @var PropertyAccessor
*/
protected $propertyAccessor;
/**
* @var string default DateTime format
*/
protected $dateTimeFormat;
/**
* @param PropelCollection $collection
* @param array $fields Fields to export
* @param string $dateTimeFormat
*/
public function __construct(PropelCollection $collection, array $fields, $dateTimeFormat = 'r')
{
$this->collection = clone $collection;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
$this->propertyPaths = [];
foreach ($fields as $name => $field) {
if (\is_string($name) && \is_string($field)) {
$this->propertyPaths[$name] = new PropertyPath($field);
} else {
$this->propertyPaths[$field] = new PropertyPath($field);
}
}
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* {@inheritdoc}
*/
public function current()
{
$current = $this->iterator->current();
$data = [];
foreach ($this->propertyPaths as $name => $propertyPath) {
$data[$name] = $this->getValue($this->propertyAccessor->getValue($current, $propertyPath));
}
return $data;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->iterator->next();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->iterator->key();
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->iterator->valid();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
if ($this->iterator) {
$this->iterator->rewind();
return;
}
$this->iterator = $this->collection->getIterator();
$this->iterator->rewind();
}
/**
* @param string $dateTimeFormat
*/
public function setDateTimeFormat($dateTimeFormat)
{
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* @return string
*/
public function getDateTimeFormat()
{
return $this->dateTimeFormat;
}
/**
* @param $value
*
* @return string|null
*/
protected function getValue($value)
{
if (\is_array($value) || $value instanceof \Traversable) {
$value = null;
} elseif ($value instanceof \DateTimeInterface) {
$value = $value->format($this->dateTimeFormat);
} elseif (\is_object($value)) {
$value = (string) $value;
}
return $value;
}
}
class_exists(\Exporter\Source\PropelCollectionSourceIterator::class);

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
interface SourceIteratorInterface extends \Iterator
{
}
interface_exists(\Exporter\Source\SourceIteratorInterface::class);

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
class SymfonySitemapSourceIterator implements SourceIteratorInterface
{
/**
* @var RouterInterface
*/
protected $router;
/**
* @var SourceIteratorInterface
*/
protected $source;
/**
* @var string
*/
protected $routeName;
/**
* @var array
*/
protected $parameters;
/**
* @param SourceIteratorInterface $source
* @param RouterInterface $router
* @param string $routeName
* @param array $parameters
*/
public function __construct(SourceIteratorInterface $source, RouterInterface $router, $routeName, array $parameters = [])
{
$this->source = $source;
$this->router = $router;
$this->routeName = $routeName;
$this->parameters = $parameters;
}
/**
* {@inheritdoc}
*/
public function current()
{
$data = $this->source->current();
$parameters = array_merge($this->parameters, array_intersect_key($data, $this->parameters));
if (!isset($data['url'])) {
$data['url'] = $this->router->generate($this->routeName, $parameters, UrlGeneratorInterface::ABSOLUTE_URL);
}
return $data;
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->source->next();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->source->key();
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->source->valid();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->source->rewind();
}
}
class_exists(\Exporter\Source\SymfonySitemapSourceIterator::class);

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
/**
* Read data from a Xml Excel file.
*
* @author Vincent Touzet <vincent.touzet@gmail.com>
*/
class XmlExcelSourceIterator extends AbstractXmlSourceIterator
{
/**
* @param string $filename
* @param bool $hasHeaders
*/
public function __construct($filename, $hasHeaders = true)
{
parent::__construct($filename, $hasHeaders);
}
/**
* {@inheritdoc}
*/
public function tagStart($parser, $name, $attributes = [])
{
switch ($name) {
case 'ss:Row':
case 'Row':
$this->bufferedRow['i_'.$this->currentRowIndex] = [];
break;
case 'ss:Cell':
case 'Cell':
// set empty values when opening Cell tag
$this->bufferedRow['i_'.$this->currentRowIndex][$this->currentColumnIndex] = '';
break;
}
}
/**
* {@inheritdoc}
*/
public function tagEnd($parser, $name)
{
switch ($name) {
case 'ss:Row':
case 'Row':
$this->currentRowIndex++;
$this->currentColumnIndex = 0;
$this->currentRowEnded = true;
break;
case 'ss:Cell':
case 'Cell':
$this->currentColumnIndex++;
break;
}
}
/**
* {@inheritdoc}
*/
public function tagContent($parser, $data)
{
$this->bufferedRow['i_'.$this->currentRowIndex][$this->currentColumnIndex] .= $data;
}
}
class_exists(\Exporter\Source\XmlExcelSourceIterator::class);

View File

@@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Source;
/**
* Read data from a Xml file.
*
* @author Vincent Touzet <vincent.touzet@gmail.com>
*/
class XmlSourceIterator extends AbstractXmlSourceIterator
{
/**
* @var string
*/
protected $mainTag;
/**
* @var string
*/
protected $dataTag;
/**
* @param string $filename
* @param string $mainTag
* @param string $dataTag
*/
public function __construct($filename, $mainTag = 'datas', $dataTag = 'data')
{
parent::__construct($filename, false);
$this->mainTag = $mainTag;
$this->dataTag = $dataTag;
}
/**
* {@inheritdoc}
*/
public function tagStart($parser, $name, $attributes = [])
{
switch ($name) {
case $this->mainTag:
break;
case $this->dataTag:
$this->bufferedRow['i_'.$this->currentRowIndex] = [];
break;
default:
if (!isset($this->columns[$this->currentColumnIndex])) {
$this->columns[$this->currentColumnIndex] = $name;
}
// set empty values when opening Cell tag
$this->bufferedRow['i_'.$this->currentRowIndex][$this->currentColumnIndex] = '';
break;
}
}
/**
* {@inheritdoc}
*/
public function tagEnd($parser, $name)
{
switch ($name) {
case $this->mainTag:
break;
case $this->dataTag:
$this->currentRowIndex++;
$this->currentColumnIndex = 0;
$this->currentRowEnded = true;
break;
default:
$this->currentColumnIndex++;
break;
}
}
/**
* {@inheritdoc}
*/
public function tagContent($parser, $data)
{
if (isset($this->bufferedRow['i_'.$this->currentRowIndex], $this->bufferedRow['i_'.$this->currentRowIndex][$this->currentColumnIndex])
) {
$this->bufferedRow['i_'.$this->currentRowIndex][$this->currentColumnIndex] .= $data;
}
}
/**
* {@inheritdoc}
*/
protected function prepareCurrentRow()
{
$this->currentRow = array_shift($this->bufferedRow);
if (\is_array($this->currentRow)) {
$datas = [];
foreach ($this->currentRow as $key => $value) {
$datas[$this->columns[$key]] = $value;
}
$this->currentRow = $datas;
}
}
}
class_exists(\Exporter\Source\XmlSourceIterator::class);

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Test;
/**
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
abstract class AbstractTypedWriterTestCase extends \PHPUnit\Framework\TestCase
{
/**
* @var WriterInterface
*/
private $writer;
protected function setUp()
{
$this->writer = $this->getWriter();
}
public function testFormatIsString()
{
$this->assertInternalType('string', $this->writer->getFormat());
}
public function testDefaultMimeTypeIsString()
{
$this->assertInternalType('string', $this->writer->getDefaultMimeType());
}
/**
* Should return a very simple instance of the writer (no need for complex
* configuration).
*
* @return WriterInterface
*/
abstract protected function getWriter();
}
class_exists(\Exporter\Test\AbstractTypedWriterTestCase::class);

View File

@@ -0,0 +1,171 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
use Sonata\Exporter\Exception\InvalidDataFormatException;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class CsvWriter implements TypedWriterInterface
{
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $delimiter;
/**
* @var string
*/
protected $enclosure;
/**
* @var string
*/
protected $escape;
/**
* @var resource
*/
protected $file;
/**
* @var bool
*/
protected $showHeaders;
/**
* @var int
*/
protected $position;
/**
* @var bool
*/
protected $withBom;
/**
* @var string
*/
private $terminate;
/**
* @param string $filename
* @param string $delimiter
* @param string $enclosure
* @param string $escape
* @param bool $showHeaders
* @param bool $withBom
* @param string $terminate
*/
public function __construct(
$filename,
$delimiter = ',',
$enclosure = '"',
$escape = '\\',
$showHeaders = true,
$withBom = false,
$terminate = "\n"
) {
$this->filename = $filename;
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
$this->escape = $escape;
$this->showHeaders = $showHeaders;
$this->terminate = $terminate;
$this->position = 0;
$this->withBom = $withBom;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exist', $filename));
}
}
/**
* {@inheritdoc}
*/
final public function getDefaultMimeType()
{
return 'text/csv';
}
/**
* {@inheritdoc}
*/
final public function getFormat()
{
return 'csv';
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->file = fopen($this->filename, 'w', false);
if ("\n" !== $this->terminate) {
stream_filter_register('filterTerminate', CsvWriterTerminate::class);
stream_filter_append($this->file, 'filterTerminate', STREAM_FILTER_WRITE, ['terminate' => $this->terminate]);
}
if (true === $this->withBom) {
fprintf($this->file, \chr(0xEF).\chr(0xBB).\chr(0xBF));
}
}
/**
* {@inheritdoc}
*/
public function close()
{
fclose($this->file);
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
if (0 == $this->position && $this->showHeaders) {
$this->addHeaders($data);
++$this->position;
}
$result = @fputcsv($this->file, $data, $this->delimiter, $this->enclosure, $this->escape);
if (!$result) {
throw new InvalidDataFormatException();
}
++$this->position;
}
/**
* @param array $data
*/
protected function addHeaders(array $data)
{
$headers = [];
foreach ($data as $header => $value) {
$headers[] = $header;
}
fputcsv($this->file, $headers, $this->delimiter, $this->enclosure, $this->escape);
}
}
class_exists(\Exporter\Writer\CsvWriter::class);

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* Filter CSV output to replace the default terminator while supporting active streams.
*/
final class CsvWriterTerminate extends \php_user_filter
{
/**
* @param $in
* @param $out
* @param $consumed
* @param $closing
*
* @return int
*/
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
if (isset($this->params['terminate'])) {
$bucket->data = preg_replace('/([^\r])\n/', '$1'.$this->params['terminate'], $bucket->data);
}
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
class_exists(\Exporter\Writer\CsvWriterTerminate::class);

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* Format boolean before use another writer.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class FormattedBoolWriter implements WriterInterface
{
/**
* @var WriterInterface
*/
protected $writer;
/**
* @var string
*/
protected $trueLabel;
/**
* @var string
*/
protected $falseLabel;
/**
* @param WriterInterface $writer
* @param string $falseLabel
* @param string $trueLabel
*/
public function __construct(WriterInterface $writer, $trueLabel = 'yes', $falseLabel = 'no')
{
$this->writer = $writer;
$this->trueLabel = $trueLabel;
$this->falseLabel = $falseLabel;
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->writer->open();
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->writer->close();
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
foreach ($data as $key => $value) {
if (\is_bool($data[$key])) {
$data[$key] = true === $data[$key] ? $this->trueLabel : $this->falseLabel;
}
}
$this->writer->write($data);
}
}
class_exists(\Exporter\Writer\FormattedBoolWriter::class);

View File

@@ -0,0 +1,161 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* Generates a GSA feed.
*
* @author Rémi Marseille <marseille@ekino.com>
*/
class GsaFeedWriter implements WriterInterface
{
const LIMIT_SIZE = 31457280; // 30MB
/**
* @var \SplFileInfo
*/
private $folder;
/**
* @var string
*/
private $dtd;
/**
* @var string
*/
private $datasource;
/**
* @var string
*/
private $feedtype;
/**
* @var int
*/
private $bufferPart;
/**
* @var resource
*/
private $buffer;
/**
* @var int
*/
private $bufferSize;
/**
* @param \SplFileInfo $folder The folder to store the generated feed(s)
* @param string $dtd A DTD URL (something like http://gsa.example.com/gsafeed.dtd)
* @param string $datasource A datasouce
* @param string $feedtype A feedtype (full|incremental|metadata-and-url)
*/
public function __construct(\SplFileInfo $folder, $dtd, $datasource, $feedtype)
{
$this->folder = $folder;
$this->dtd = $dtd;
$this->datasource = $datasource;
$this->feedtype = $feedtype;
$this->bufferPart = 0;
$this->bufferSize = 0;
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->generateNewPart();
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
$line = sprintf(" <record url=\"%s\" mimetype=\"%s\" action=\"%s\"/>\n",
$data['url'],
$data['mime_type'],
$data['action']
);
// + 18 corresponding to the length of the closing tags
if (($this->bufferSize + \strlen($line) + 18) > self::LIMIT_SIZE) {
$this->generateNewPart();
}
$this->bufferSize += fwrite($this->buffer, $line);
}
/**
* {@inheritdoc}
*/
public function close()
{
if ($this->buffer) {
$this->closeFeed();
}
}
/**
* Generates a new file.
*
* @throws \RuntimeException
*/
private function generateNewPart()
{
if ($this->buffer) {
$this->closeFeed();
}
$this->bufferSize = 0;
++$this->bufferPart;
if (!is_writable($this->folder)) {
throw new \RuntimeException(sprintf('Unable to write to folder: %s', $this->folder));
}
$this->buffer = fopen(sprintf('%s/feed_%05d.xml', $this->folder, $this->bufferPart), 'w');
$this->bufferSize += fwrite($this->buffer, <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE gsafeed PUBLIC "-//Google//DTD GSA Feeds//EN" "$this->dtd">
<gsafeed>
<header>
<datasource>$this->datasource</datasource>
<feedtype>$this->feedtype</feedtype>
</header>
<group>
XML
);
}
/**
* Closes the current feed.
*/
private function closeFeed()
{
fwrite($this->buffer, <<<'EOF'
</group>
</gsafeed>
EOF
);
fclose($this->buffer);
}
}
class_exists(\Exporter\Writer\GsaFeedWriter::class);

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
class InMemoryWriter implements WriterInterface
{
/**
* @var array
*/
protected $elements;
/**
* {@inheritdoc}
*/
public function open()
{
$this->elements = [];
}
/**
* {@inheritdoc}
*/
public function close()
{
return $this->elements;
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
$this->elements[] = $data;
}
/**
* @return array
*/
public function getElements()
{
return $this->elements;
}
}
class_exists(\Exporter\Writer\InMemoryWriter::class);

View File

@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class JsonWriter implements TypedWriterInterface
{
/**
* @var string
*/
protected $filename;
/**
* @var resource
*/
protected $file;
/**
* @var int
*/
protected $position;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
$this->position = 0;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exist', $filename));
}
}
/**
* {@inheritdoc}
*/
final public function getDefaultMimeType()
{
return 'application/json';
}
/**
* {@inheritdoc}
*/
final public function getFormat()
{
return 'json';
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->file = fopen($this->filename, 'w', false);
fwrite($this->file, '[');
}
/**
* {@inheritdoc}
*/
public function close()
{
fwrite($this->file, ']');
fclose($this->file);
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
fwrite($this->file, ($this->position > 0 ? ',' : '').json_encode($data));
++$this->position;
}
}
class_exists(\Exporter\Writer\JsonWriter::class);

View File

@@ -0,0 +1,369 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* Generates a sitemap site from.
*/
class SitemapWriter implements WriterInterface
{
const LIMIT_SIZE = 10485760;
const LIMIT_URL = 50000;
/**
* @var string
*/
protected $folder;
/**
* @var string
*/
protected $pattern;
/**
* @var string
*/
protected $groupName;
/**
* @var bool
*/
protected $autoIndex;
/**
* @var resource
*/
protected $buffer;
/**
* @var array
*/
protected $headers;
/**
* @var int
*/
protected $bufferSize = 0;
/**
* @var int
*/
protected $bufferUrlCount = 0;
/**
* @var int
*/
protected $bufferPart = 0;
/**
* @param string $folder The folder to store the sitemap.xml file
* @param mixed $groupName Name of sub-sitemap (optional)
* @param array $headers Indicate the need for namespace in the header sitemap
* @param bool $autoIndex If you want to generate index of sitemap (optional)
*/
public function __construct($folder, $groupName = false, array $headers = [], $autoIndex = true)
{
$this->folder = $folder;
$this->groupName = \is_string($groupName) ? $groupName : '';
$this->headers = $headers;
$this->autoIndex = $autoIndex;
$this->pattern = 'sitemap_'.($this->groupName ? $this->groupName.'_' : '').'%05d.xml';
}
/**
* Returns the status of auto generation of index site map.
*
* @return bool
*/
public function isAutoIndex()
{
return $this->autoIndex;
}
/**
* Returns folder to store the sitemap.xml file.
*
* @return string
*/
public function getFolder()
{
return $this->folder;
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->bufferPart = 0;
$this->generateNewPart();
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
$data = $this->buildData($data);
switch ($data['type']) {
case 'video':
$line = $this->generateVideoLine($data);
break;
case 'image':
$line = $this->generateImageLine($data);
break;
case 'default':
default:
$line = $this->generateDefaultLine($data);
}
$this->addSitemapLine($line);
}
/**
* {@inheritdoc}
*/
public function close()
{
if ($this->buffer) {
$this->closeSitemap();
}
if ($this->autoIndex) {
self::generateSitemapIndex(
$this->folder,
'sitemap_'.($this->groupName ? $this->groupName.'_' : '').'*.xml',
'sitemap'.($this->groupName ? '_'.$this->groupName : '').'.xml'
);
}
}
/**
* Generates the sitemap index from the sitemap part avaible in the folder.
*
* @param string $folder A folder to write sitemap index
* @param string $baseUrl A base URL
* @param string $pattern A sitemap pattern, optional
* @param string $filename A sitemap file name, optional
*/
public static function generateSitemapIndex($folder, $baseUrl, $pattern = 'sitemap*.xml', $filename = 'sitemap.xml')
{
$content = "<?xml version='1.0' encoding='UTF-8'?".">\n<sitemapindex xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://www.sitemaps.org/schemas/sitemap/1.0 http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd' xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n";
foreach (glob(sprintf('%s/%s', $folder, $pattern)) as $file) {
$stat = stat($file);
$content .= sprintf("\t".'<sitemap><loc>%s/%s</loc><lastmod>%s</lastmod></sitemap>'."\n",
$baseUrl,
basename($file),
date('Y-m-d', $stat['mtime'])
);
}
$content .= '</sitemapindex>';
file_put_contents(sprintf('%s/%s', $folder, $filename), $content);
}
/**
* Generate a new sitemap part.
*
* @throws \RuntimeException
*/
protected function generateNewPart()
{
if ($this->buffer) {
$this->closeSitemap();
}
$this->bufferUrlCount = 0;
$this->bufferSize = 0;
++$this->bufferPart;
if (!is_writable($this->folder)) {
throw new \RuntimeException(sprintf('Unable to write to folder: %s', $this->folder));
}
$filename = sprintf($this->pattern, $this->bufferPart);
$this->buffer = fopen($this->folder.'/'.$filename, 'w');
$this->bufferSize += fwrite($this->buffer, '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"'.$this->getHeaderByFlag().'>'."\n");
}
/**
* Add a new line into the sitemap part.
*
* @param string $line
*/
protected function addSitemapLine($line)
{
if ($this->bufferUrlCount >= self::LIMIT_URL) {
$this->generateNewPart();
}
if (($this->bufferSize + \strlen($line) + 9) > self::LIMIT_SIZE) {
$this->generateNewPart();
}
++$this->bufferUrlCount;
$this->bufferSize += fwrite($this->buffer, $line);
}
/**
* Build data with default parameters.
*
* @param array $data List of parameters
*
* @return array
*/
protected function buildData(array $data)
{
$default = [
'url' => null,
'lastmod' => 'now',
'changefreq' => 'weekly',
'priority' => 0.5,
'type' => 'default',
];
$data = array_merge($default, $data);
$this->fixDataType($data);
return $data;
}
/**
* Fix type of data, if data type is specific,
* he must to be defined in data and he must to be a array.
*
* @param array &$data List of parameters
*/
protected function fixDataType(array &$data)
{
if ('default' === $data['type']) {
return;
}
$valid_var_name = [
'image' => 'images',
'video' => 'video',
];
if (!isset($valid_var_name[$data['type']], $data[$valid_var_name[$data['type']]]) || !\is_array($data[$valid_var_name[$data['type']]])) {
$data['type'] = 'default';
}
}
/**
* Generate standard line of sitemap.
*
* @param array $data List of parameters
*
* @return string
*/
protected function generateDefaultLine(array $data)
{
return sprintf(' '.'<url><loc>%s</loc><lastmod>%s</lastmod><changefreq>%s</changefreq><priority>%s</priority></url>'."\n", $data['url'], date('Y-m-d', strtotime($data['lastmod'])), $data['changefreq'], $data['priority']);
}
/**
* Generate image line of sitemap.
*
* @param array $data List of parameters
*
* @return string
*/
protected function generateImageLine(array $data)
{
$images = '';
if (\count($data['images']) > 1000) {
$data['images'] = array_splice($data['images'], 1000);
}
$builder = [
'url' => 'loc',
'location' => 'geo_location',
];
foreach ($data['images'] as $image) {
$images .= '<image:image>';
foreach ($image as $key => $element) {
$images .= sprintf('<image:%1$s>%2$s</image:%1$s>', (isset($builder[$key]) ? $builder[$key] : $key), $element);
}
$images .= '</image:image>';
}
return sprintf(' '.'<url><loc>%s</loc>%s</url>'."\n", $data['url'], $images);
}
/**
* Generate video line of sitemap.
*
* @param array $data List of parameters
*
* @return string
*/
protected function generateVideoLine(array $data)
{
$videos = '';
$builder = [
'thumbnail' => 'thumbnail_loc',
];
foreach ($data['video'] as $key => $video) {
$videos .= sprintf('<video:%1$s>%2$s</video:%1$s>', (isset($builder[$key]) ? $builder[$key] : $key), $video);
}
return sprintf(' '.'<url><loc>%s</loc><video:video>%s</video:video></url>'."\n", $data['url'], $videos);
}
/**
* Generate additional header with namespace adapted to the content.
*
* @return string
*/
protected function getHeaderByFlag()
{
$namespaces = [
'video' => 'xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"',
'image' => 'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"',
];
$result = '';
foreach ($this->headers as $flag) {
$result .= ' '.$namespaces[$flag];
}
return $result;
}
/**
* Close the sitemap part.
*/
protected function closeSitemap()
{
fwrite($this->buffer, '</urlset>');
fclose($this->buffer);
}
}
class_exists(\Exporter\Writer\SitemapWriter::class);

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
interface TypedWriterInterface extends WriterInterface
{
/**
* There can be several mime types for a given format, this method should
* return the most appopriate / popular one.
*
* @return string the mime type of the output
*/
public function getDefaultMimeType();
/**
* Returns a string best describing the format of the output (the file
* extension is fine, for example).
*
* @return string a string without spaces, usable in a translation string
*/
public function getFormat();
}
interface_exists(\Exporter\Writer\TypedWriterInterface::class);

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
interface WriterInterface
{
public function open();
/**
* @param array $data
*/
public function write(array $data);
public function close();
}
interface_exists(\Exporter\Writer\WriterInterface::class);

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class XlsWriter implements TypedWriterInterface
{
/**
* @var string
*/
protected $filename;
/**
* @var resource
*/
protected $file;
/**
* @var bool
*/
protected $showHeaders;
/**
* @var int
*/
protected $position;
/**
* @param $filename
* @param bool $showHeaders
*
* @throws \RuntimeException
*/
public function __construct($filename, $showHeaders = true)
{
$this->filename = $filename;
$this->showHeaders = $showHeaders;
$this->position = 0;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exist', $filename));
}
}
/**
* {@inheritdoc}
*/
final public function getDefaultMimeType()
{
return 'application/vnd.ms-excel';
}
/**
* {@inheritdoc}
*/
final public function getFormat()
{
return 'xls';
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->file = fopen($this->filename, 'w', false);
fwrite($this->file, '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta name=ProgId content=Excel.Sheet><meta name=Generator content="https://github.com/sonata-project/exporter"></head><body><table>');
}
/**
* {@inheritdoc}
*/
public function close()
{
fwrite($this->file, '</table></body></html>');
fclose($this->file);
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
$this->init($data);
fwrite($this->file, '<tr>');
foreach ($data as $value) {
fwrite($this->file, sprintf('<td>%s</td>', $value));
}
fwrite($this->file, '</tr>');
++$this->position;
}
/**
* @param $data
*/
protected function init($data)
{
if ($this->position > 0) {
return;
}
if ($this->showHeaders) {
fwrite($this->file, '<tr>');
foreach ($data as $header => $value) {
fwrite($this->file, sprintf('<th>%s</th>', $header));
}
fwrite($this->file, '</tr>');
++$this->position;
}
}
}
class_exists(\Exporter\Writer\XlsWriter::class);

View File

@@ -0,0 +1,155 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
/**
* Generate a Xml Excel file.
*
* @author Vincent Touzet <vincent.touzet@gmail.com>
*/
class XmlExcelWriter implements WriterInterface
{
/**
* @var string|null
*/
protected $filename = null;
/**
* @var resource|null
*/
protected $file = null;
/**
* @var bool
*/
protected $showHeaders;
/**
* @var mixed|null
*/
protected $columnsType = null;
/**
* @var int
*/
protected $position = 0;
/**
* @var string
*/
protected $header = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:x2="http://schemas.microsoft.com/office/excel/2003/xml" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"><OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"></OfficeDocumentSettings><ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"></ExcelWorkbook><Worksheet ss:Name="Sheet 1"><Table>';
protected $footer = '</Table></Worksheet></Workbook>';
/**
* @param string $filename
* @param bool $showHeaders
* @param mixed $columnsType Define cells type to use
* If string: force all cells to the given type. e.g: 'Number'
* If array: force only given cells. e.g: array('ean'=>'String', 'price'=>'Number')
* If null: will guess the type. 'Number' if value is numeric, 'String' otherwise
*/
public function __construct($filename, $showHeaders = true, $columnsType = null)
{
$this->filename = $filename;
$this->showHeaders = $showHeaders;
$this->columnsType = $columnsType;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exist', $filename));
}
}
public function open()
{
$this->file = fopen($this->filename, 'w');
fwrite($this->file, $this->header);
}
/**
* @param array $data
*/
public function write(array $data)
{
if (0 == $this->position && $this->showHeaders) {
$header = array_keys($data);
fwrite($this->file, $this->getXmlString($header));
++$this->position;
}
fwrite($this->file, $this->getXmlString($data));
++$this->position;
}
public function close()
{
fwrite($this->file, $this->footer);
fclose($this->file);
}
/**
* Prepare and return XML string for MS Excel XML from array.
*
* @param array $fields
*
* @return string
*/
private function getXmlString(array $fields = [])
{
$xmlData = [];
$xmlData[] = '<Row>';
foreach ($fields as $key => $value) {
$value = htmlspecialchars($value);
$value = str_replace(["\r\n", "\r", "\n"], '&#10;', $value);
$dataType = 'String';
if (0 != $this->position || !$this->showHeaders) {
$dataType = $this->getDataType($key, $value);
}
$xmlData[] = '<Cell><Data ss:Type="'.$dataType.'">'.$value.'</Data></Cell>';
}
$xmlData[] = '</Row>';
return implode('', $xmlData);
}
/**
* @param string $key
* @param string $value
*
* @return string
*/
private function getDataType($key, $value)
{
$dataType = null;
if (null !== $this->columnsType) {
if (\is_string($this->columnsType)) {
$dataType = $this->columnsType;
} elseif (\is_array($this->columnsType)) {
if (\array_key_exists($key, $this->columnsType)) {
$dataType = $this->columnsType[$key];
}
}
}
if (null === $dataType) {
// guess the type
if (is_numeric($value)) {
$dataType = 'Number';
} else {
$dataType = 'String';
}
}
return $dataType;
}
}
class_exists(\Exporter\Writer\XmlExcelWriter::class);

View File

@@ -0,0 +1,129 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Exporter\Writer;
use Sonata\Exporter\Exception\InvalidDataFormatException;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class XmlWriter implements TypedWriterInterface
{
/**
* @var string
*/
protected $filename;
/**
* @var resource
*/
protected $file;
/**
* @var int
*/
protected $position;
/**
* @var string
*/
protected $mainElement;
/**
* @var string
*/
protected $childElement;
/**
* @param string $filename
* @param string $mainElement
* @param string $childElement
*/
public function __construct($filename, $mainElement = 'datas', $childElement = 'data')
{
$this->filename = $filename;
$this->position = 0;
$this->mainElement = $mainElement;
$this->childElement = $childElement;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exist', $filename));
}
}
/**
* {@inheritdoc}
*/
final public function getDefaultMimeType()
{
return 'text/xml';
}
/**
* {@inheritdoc}
*/
final public function getFormat()
{
return 'xml';
}
/**
* {@inheritdoc}
*/
public function open()
{
$this->file = fopen($this->filename, 'w', false);
fwrite($this->file, sprintf("<?xml version=\"1.0\" ?>\n<%s>\n", $this->mainElement));
}
/**
* {@inheritdoc}
*/
public function close()
{
fwrite($this->file, sprintf('</%s>', $this->mainElement));
fclose($this->file);
}
/**
* {@inheritdoc}
*/
public function write(array $data)
{
fwrite($this->file, sprintf("<%s>\n", $this->childElement));
foreach ($data as $k => $v) {
$this->generateNode($k, $v);
}
fwrite($this->file, sprintf("</%s>\n", $this->childElement));
}
/**
* @param string $name
* @param string $value
*/
protected function generateNode($name, $value)
{
if (\is_array($value)) {
throw new \RuntimeException('Not implemented');
} elseif (is_scalar($value) || null === $value) {
fwrite($this->file, sprintf("<%s><![CDATA[%s]]></%s>\n", $name, $value, $name));
} else {
throw new InvalidDataFormatException('Invalid data');
}
}
}
class_exists(\Exporter\Writer\XmlWriter::class);