default services conflit ?
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Exception;
|
||||
|
||||
/**
|
||||
* Contains some helper functions used by exceptions in this project.
|
||||
*/
|
||||
abstract class AbstractDataFormatException extends \Exception
|
||||
{
|
||||
/**
|
||||
* Return a description of the data type represented by the provided parameter.
|
||||
*
|
||||
* @param \ReflectionClass $data The data type to describe. Note that
|
||||
* \ArrayObject is used as a proxy to mean an array primitive (or an ArrayObject).
|
||||
* @return string
|
||||
*/
|
||||
protected static function describeDataType($data)
|
||||
{
|
||||
if (is_array($data) || ($data instanceof \ReflectionClass)) {
|
||||
if (is_array($data) || ($data->getName() == 'ArrayObject')) {
|
||||
return 'an array';
|
||||
}
|
||||
return 'an instance of ' . $data->getName();
|
||||
}
|
||||
if (is_string($data)) {
|
||||
return 'a string';
|
||||
}
|
||||
if (is_object($data)) {
|
||||
return 'an instance of ' . get_class($data);
|
||||
}
|
||||
throw new \Exception("Undescribable data error: " . var_export($data, true));
|
||||
}
|
||||
|
||||
protected static function describeAllowedTypes($allowedTypes)
|
||||
{
|
||||
if (is_array($allowedTypes) && !empty($allowedTypes)) {
|
||||
if (count($allowedTypes) > 1) {
|
||||
return static::describeListOfAllowedTypes($allowedTypes);
|
||||
}
|
||||
$allowedTypes = $allowedTypes[0];
|
||||
}
|
||||
return static::describeDataType($allowedTypes);
|
||||
}
|
||||
|
||||
protected static function describeListOfAllowedTypes($allowedTypes)
|
||||
{
|
||||
$descriptions = [];
|
||||
foreach ($allowedTypes as $oneAllowedType) {
|
||||
$descriptions[] = static::describeDataType($oneAllowedType);
|
||||
}
|
||||
if (count($descriptions) == 2) {
|
||||
return "either {$descriptions[0]} or {$descriptions[1]}";
|
||||
}
|
||||
$lastDescription = array_pop($descriptions);
|
||||
$otherDescriptions = implode(', ', $descriptions);
|
||||
return "one of $otherDescriptions or $lastDescription";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Exception;
|
||||
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Represents an incompatibility between the output data and selected formatter.
|
||||
*/
|
||||
class IncompatibleDataException extends AbstractDataFormatException
|
||||
{
|
||||
public function __construct(FormatterInterface $formatter, $data, $allowedTypes)
|
||||
{
|
||||
$formatterDescription = get_class($formatter);
|
||||
$dataDescription = static::describeDataType($data);
|
||||
$allowedTypesDescription = static::describeAllowedTypes($allowedTypes);
|
||||
$message = "Data provided to $formatterDescription must be $allowedTypesDescription. Instead, $dataDescription was provided.";
|
||||
parent::__construct($message, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Exception;
|
||||
|
||||
/**
|
||||
* Represents an incompatibility between the output data and selected formatter.
|
||||
*/
|
||||
class InvalidFormatException extends AbstractDataFormatException
|
||||
{
|
||||
public function __construct($format, $data, $validFormats)
|
||||
{
|
||||
$dataDescription = static::describeDataType($data);
|
||||
$message = "The format $format cannot be used with the data produced by this command, which was $dataDescription. Valid formats are: " . implode(',', $validFormats);
|
||||
parent::__construct($message, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Exception;
|
||||
|
||||
/**
|
||||
* Indicates that the requested format does not exist.
|
||||
*/
|
||||
class UnknownFieldException extends \Exception
|
||||
{
|
||||
public function __construct($field)
|
||||
{
|
||||
$message = "The requested field, '$field', is not defined.";
|
||||
parent::__construct($message, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Exception;
|
||||
|
||||
/**
|
||||
* Indicates that the requested format does not exist.
|
||||
*/
|
||||
class UnknownFormatException extends \Exception
|
||||
{
|
||||
public function __construct($format)
|
||||
{
|
||||
$message = "The requested format, '$format', is not available.";
|
||||
parent::__construct($message, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
|
||||
use Consolidation\OutputFormatters\Exception\InvalidFormatException;
|
||||
use Consolidation\OutputFormatters\Exception\UnknownFormatException;
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
|
||||
use Consolidation\OutputFormatters\Formatters\MetadataFormatterInterface;
|
||||
use Consolidation\OutputFormatters\Formatters\RenderDataInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\MetadataInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\DomToArraySimplifier;
|
||||
use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\SimplifyToArrayInterface;
|
||||
use Consolidation\OutputFormatters\Validate\ValidationInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\ListDataFromKeys;
|
||||
use Consolidation\OutputFormatters\StructuredData\ConversionInterface;
|
||||
use Consolidation\OutputFormatters\Formatters\HumanReadableFormat;
|
||||
|
||||
/**
|
||||
* Manage a collection of formatters; return one on request.
|
||||
*/
|
||||
class FormatterManager
|
||||
{
|
||||
/** var FormatterInterface[] */
|
||||
protected $formatters = [];
|
||||
/** var SimplifyToArrayInterface[] */
|
||||
protected $arraySimplifiers = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function addDefaultFormatters()
|
||||
{
|
||||
$defaultFormatters = [
|
||||
'null' => '\Consolidation\OutputFormatters\Formatters\NoOutputFormatter',
|
||||
'string' => '\Consolidation\OutputFormatters\Formatters\StringFormatter',
|
||||
'yaml' => '\Consolidation\OutputFormatters\Formatters\YamlFormatter',
|
||||
'xml' => '\Consolidation\OutputFormatters\Formatters\XmlFormatter',
|
||||
'json' => '\Consolidation\OutputFormatters\Formatters\JsonFormatter',
|
||||
'print-r' => '\Consolidation\OutputFormatters\Formatters\PrintRFormatter',
|
||||
'php' => '\Consolidation\OutputFormatters\Formatters\SerializeFormatter',
|
||||
'var_export' => '\Consolidation\OutputFormatters\Formatters\VarExportFormatter',
|
||||
'list' => '\Consolidation\OutputFormatters\Formatters\ListFormatter',
|
||||
'csv' => '\Consolidation\OutputFormatters\Formatters\CsvFormatter',
|
||||
'tsv' => '\Consolidation\OutputFormatters\Formatters\TsvFormatter',
|
||||
'table' => '\Consolidation\OutputFormatters\Formatters\TableFormatter',
|
||||
'sections' => '\Consolidation\OutputFormatters\Formatters\SectionsFormatter',
|
||||
];
|
||||
if (class_exists('Symfony\Component\VarDumper\Dumper\CliDumper')) {
|
||||
$defaultFormatters['var_dump'] = '\Consolidation\OutputFormatters\Formatters\VarDumpFormatter';
|
||||
}
|
||||
foreach ($defaultFormatters as $id => $formatterClassname) {
|
||||
$formatter = new $formatterClassname;
|
||||
$this->addFormatter($id, $formatter);
|
||||
}
|
||||
$this->addFormatter('', $this->formatters['string']);
|
||||
}
|
||||
|
||||
public function addDefaultSimplifiers()
|
||||
{
|
||||
// Add our default array simplifier (DOMDocument to array)
|
||||
$this->addSimplifier(new DomToArraySimplifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a formatter
|
||||
*
|
||||
* @param string $key the identifier of the formatter to add
|
||||
* @param string $formatter the class name of the formatter to add
|
||||
* @return FormatterManager
|
||||
*/
|
||||
public function addFormatter($key, FormatterInterface $formatter)
|
||||
{
|
||||
$this->formatters[$key] = $formatter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a simplifier
|
||||
*
|
||||
* @param SimplifyToArrayInterface $simplifier the array simplifier to add
|
||||
* @return FormatterManager
|
||||
*/
|
||||
public function addSimplifier(SimplifyToArrayInterface $simplifier)
|
||||
{
|
||||
$this->arraySimplifiers[] = $simplifier;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a set of InputOption based on the annotations of a command.
|
||||
* @param FormatterOptions $options
|
||||
* @return InputOption[]
|
||||
*/
|
||||
public function automaticOptions(FormatterOptions $options, $dataType)
|
||||
{
|
||||
$automaticOptions = [];
|
||||
|
||||
// At the moment, we only support automatic options for --format
|
||||
// and --fields, so exit if the command returns no data.
|
||||
if (!isset($dataType)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$validFormats = $this->validFormats($dataType);
|
||||
if (empty($validFormats)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$availableFields = $options->get(FormatterOptions::FIELD_LABELS);
|
||||
$hasDefaultStringField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD);
|
||||
$defaultFormat = $hasDefaultStringField ? 'string' : ($availableFields ? 'table' : 'yaml');
|
||||
|
||||
if (count($validFormats) > 1) {
|
||||
// Make an input option for --format
|
||||
$description = 'Format the result data. Available formats: ' . implode(',', $validFormats);
|
||||
$automaticOptions[FormatterOptions::FORMAT] = new InputOption(FormatterOptions::FORMAT, '', InputOption::VALUE_REQUIRED, $description, $defaultFormat);
|
||||
}
|
||||
|
||||
$dataTypeClass = ($dataType instanceof \ReflectionClass) ? $dataType : new \ReflectionClass($dataType);
|
||||
|
||||
if ($availableFields) {
|
||||
$defaultFields = $options->get(FormatterOptions::DEFAULT_FIELDS, [], '');
|
||||
$description = 'Available fields: ' . implode(', ', $this->availableFieldsList($availableFields));
|
||||
$automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, $description, $defaultFields);
|
||||
} elseif ($dataTypeClass->implementsInterface('Consolidation\OutputFormatters\StructuredData\RestructureInterface')) {
|
||||
$automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, 'Limit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".', []);
|
||||
}
|
||||
|
||||
if (isset($automaticOptions[FormatterOptions::FIELDS])) {
|
||||
$automaticOptions[FormatterOptions::FIELD] = new InputOption(FormatterOptions::FIELD, '', InputOption::VALUE_REQUIRED, "Select just one field, and force format to *string*.", '');
|
||||
}
|
||||
|
||||
return $automaticOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of available fields, return a list of field descriptions.
|
||||
* @return string[]
|
||||
*/
|
||||
protected function availableFieldsList($availableFields)
|
||||
{
|
||||
return array_map(
|
||||
function ($key) use ($availableFields) {
|
||||
return $availableFields[$key] . " ($key)";
|
||||
},
|
||||
array_keys($availableFields)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identifiers for all valid data types that have been registered.
|
||||
*
|
||||
* @param mixed $dataType \ReflectionObject or other description of the produced data type
|
||||
* @return array
|
||||
*/
|
||||
public function validFormats($dataType)
|
||||
{
|
||||
$validFormats = [];
|
||||
foreach ($this->formatters as $formatId => $formatterName) {
|
||||
$formatter = $this->getFormatter($formatId);
|
||||
if (!empty($formatId) && $this->isValidFormat($formatter, $dataType)) {
|
||||
$validFormats[] = $formatId;
|
||||
}
|
||||
}
|
||||
sort($validFormats);
|
||||
return $validFormats;
|
||||
}
|
||||
|
||||
public function isValidFormat(FormatterInterface $formatter, $dataType)
|
||||
{
|
||||
if (is_array($dataType)) {
|
||||
$dataType = new \ReflectionClass('\ArrayObject');
|
||||
}
|
||||
if (!is_object($dataType) && !class_exists($dataType)) {
|
||||
return false;
|
||||
}
|
||||
if (!$dataType instanceof \ReflectionClass) {
|
||||
$dataType = new \ReflectionClass($dataType);
|
||||
}
|
||||
return $this->isValidDataType($formatter, $dataType);
|
||||
}
|
||||
|
||||
public function isValidDataType(FormatterInterface $formatter, \ReflectionClass $dataType)
|
||||
{
|
||||
if ($this->canSimplifyToArray($dataType)) {
|
||||
if ($this->isValidFormat($formatter, [])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If the formatter does not implement ValidationInterface, then
|
||||
// it is presumed that the formatter only accepts arrays.
|
||||
if (!$formatter instanceof ValidationInterface) {
|
||||
return $dataType->isSubclassOf('ArrayObject') || ($dataType->getName() == 'ArrayObject');
|
||||
}
|
||||
return $formatter->isValidDataType($dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format and write output
|
||||
*
|
||||
* @param OutputInterface $output Output stream to write to
|
||||
* @param string $format Data format to output in
|
||||
* @param mixed $structuredOutput Data to output
|
||||
* @param FormatterOptions $options Formatting options
|
||||
*/
|
||||
public function write(OutputInterface $output, $format, $structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
// Convert the data to another format (e.g. converting from RowsOfFields to
|
||||
// UnstructuredListData when the fields indicate an unstructured transformation
|
||||
// is requested).
|
||||
$structuredOutput = $this->convertData($structuredOutput, $options);
|
||||
|
||||
// TODO: If the $format is the default format (not selected by the user), and
|
||||
// if `convertData` switched us to unstructured data, then select a new default
|
||||
// format (e.g. yaml) if the selected format cannot render the converted data.
|
||||
$formatter = $this->getFormatter((string)$format);
|
||||
|
||||
// If the data format is not applicable for the selected formatter, throw an error.
|
||||
if (!is_string($structuredOutput) && !$this->isValidFormat($formatter, $structuredOutput)) {
|
||||
$validFormats = $this->validFormats($structuredOutput);
|
||||
throw new InvalidFormatException((string)$format, $structuredOutput, $validFormats);
|
||||
}
|
||||
if ($structuredOutput instanceof FormatterAwareInterface) {
|
||||
$structuredOutput->setFormatter($formatter);
|
||||
}
|
||||
// Give the formatter a chance to override the options
|
||||
$options = $this->overrideOptions($formatter, $structuredOutput, $options);
|
||||
$restructuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options);
|
||||
if ($formatter instanceof MetadataFormatterInterface) {
|
||||
$formatter->writeMetadata($output, $structuredOutput, $options);
|
||||
}
|
||||
$formatter->write($output, $restructuredOutput, $options);
|
||||
}
|
||||
|
||||
protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
// Give the formatter a chance to do something with the
|
||||
// raw data before it is restructured.
|
||||
$overrideRestructure = $this->overrideRestructure($formatter, $structuredOutput, $options);
|
||||
if ($overrideRestructure) {
|
||||
return $overrideRestructure;
|
||||
}
|
||||
|
||||
// Restructure the output data (e.g. select fields to display, etc.).
|
||||
$restructuredOutput = $this->restructureData($structuredOutput, $options);
|
||||
|
||||
// Make sure that the provided data is in the correct format for the selected formatter.
|
||||
$restructuredOutput = $this->validateData($formatter, $restructuredOutput, $options);
|
||||
|
||||
// Give the original data a chance to re-render the structured
|
||||
// output after it has been restructured and validated.
|
||||
$restructuredOutput = $this->renderData($formatter, $structuredOutput, $restructuredOutput, $options);
|
||||
|
||||
return $restructuredOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the requested formatter.
|
||||
*
|
||||
* @param string $format Identifier for requested formatter
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function getFormatter($format)
|
||||
{
|
||||
// The client must inject at least one formatter before asking for
|
||||
// any formatters; if not, we will provide all of the usual defaults
|
||||
// as a convenience.
|
||||
if (empty($this->formatters)) {
|
||||
$this->addDefaultFormatters();
|
||||
$this->addDefaultSimplifiers();
|
||||
}
|
||||
if (!$this->hasFormatter($format)) {
|
||||
throw new UnknownFormatException($format);
|
||||
}
|
||||
$formatter = $this->formatters[$format];
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the stipulated format exists
|
||||
*/
|
||||
public function hasFormatter($format)
|
||||
{
|
||||
return array_key_exists($format, $this->formatters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the data as necessary (e.g. to select or reorder fields).
|
||||
*
|
||||
* @param FormatterInterface $formatter
|
||||
* @param mixed $originalData
|
||||
* @param mixed $restructuredData
|
||||
* @param FormatterOptions $options Formatting options
|
||||
* @return mixed
|
||||
*/
|
||||
public function renderData(FormatterInterface $formatter, $originalData, $restructuredData, FormatterOptions $options)
|
||||
{
|
||||
if ($formatter instanceof RenderDataInterface) {
|
||||
return $formatter->renderData($originalData, $restructuredData, $options);
|
||||
}
|
||||
return $restructuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the provided data is compatible with the formatter being used.
|
||||
*
|
||||
* @param FormatterInterface $formatter Formatter being used
|
||||
* @param mixed $structuredOutput Data to validate
|
||||
* @return mixed
|
||||
*/
|
||||
public function validateData(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
// If the formatter implements ValidationInterface, then let it
|
||||
// test the data and throw or return an error
|
||||
if ($formatter instanceof ValidationInterface) {
|
||||
return $formatter->validate($structuredOutput);
|
||||
}
|
||||
// If the formatter does not implement ValidationInterface, then
|
||||
// it will never be passed an ArrayObject; we will always give
|
||||
// it a simple array.
|
||||
$structuredOutput = $this->simplifyToArray($structuredOutput, $options);
|
||||
// If we could not simplify to an array, then throw an exception.
|
||||
// We will never give a formatter anything other than an array
|
||||
// unless it validates that it can accept the data type.
|
||||
if (!is_array($structuredOutput)) {
|
||||
throw new IncompatibleDataException(
|
||||
$formatter,
|
||||
$structuredOutput,
|
||||
[]
|
||||
);
|
||||
}
|
||||
return $structuredOutput;
|
||||
}
|
||||
|
||||
protected function simplifyToArray($structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
// We can do nothing unless the provided data is an object.
|
||||
if (!is_object($structuredOutput)) {
|
||||
return $structuredOutput;
|
||||
}
|
||||
// Check to see if any of the simplifiers can convert the given data
|
||||
// set to an array.
|
||||
$outputDataType = new \ReflectionClass($structuredOutput);
|
||||
foreach ($this->arraySimplifiers as $simplifier) {
|
||||
if ($simplifier->canSimplify($outputDataType)) {
|
||||
$structuredOutput = $simplifier->simplifyToArray($structuredOutput, $options);
|
||||
}
|
||||
}
|
||||
// Convert data structure back into its original form, if necessary.
|
||||
if ($structuredOutput instanceof OriginalDataInterface) {
|
||||
return $structuredOutput->getOriginalData();
|
||||
}
|
||||
// Convert \ArrayObjects to a simple array.
|
||||
if ($structuredOutput instanceof \ArrayObject) {
|
||||
return $structuredOutput->getArrayCopy();
|
||||
}
|
||||
return $structuredOutput;
|
||||
}
|
||||
|
||||
protected function canSimplifyToArray(\ReflectionClass $structuredOutput)
|
||||
{
|
||||
foreach ($this->arraySimplifiers as $simplifier) {
|
||||
if ($simplifier->canSimplify($structuredOutput)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from one format to another if necessary prior to restructuring.
|
||||
*/
|
||||
public function convertData($structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
if ($structuredOutput instanceof ConversionInterface) {
|
||||
return $structuredOutput->convert($options);
|
||||
}
|
||||
return $structuredOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restructure the data as necessary (e.g. to select or reorder fields).
|
||||
*
|
||||
* @param mixed $structuredOutput
|
||||
* @param FormatterOptions $options
|
||||
* @return mixed
|
||||
*/
|
||||
public function restructureData($structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
if ($structuredOutput instanceof RestructureInterface) {
|
||||
return $structuredOutput->restructure($options);
|
||||
}
|
||||
return $structuredOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the formatter access to the raw structured data prior
|
||||
* to restructuring. For example, the 'list' formatter may wish
|
||||
* to display the row keys when provided table output. If this
|
||||
* function returns a result that does not evaluate to 'false',
|
||||
* then that result will be used as-is, and restructuring and
|
||||
* validation will not occur.
|
||||
*
|
||||
* @param mixed $structuredOutput
|
||||
* @param FormatterOptions $options
|
||||
* @return mixed
|
||||
*/
|
||||
public function overrideRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
if ($formatter instanceof OverrideRestructureInterface) {
|
||||
return $formatter->overrideRestructure($structuredOutput, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the formatter to mess with the configuration options before any
|
||||
* transformations et. al. get underway.
|
||||
* @param FormatterInterface $formatter
|
||||
* @param mixed $structuredOutput
|
||||
* @param FormatterOptions $options
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
public function overrideOptions(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
// Set the "Human Readable" option if the formatter has the HumanReadable marker interface
|
||||
if ($formatter instanceof HumanReadableFormat) {
|
||||
$options->setHumanReadable();
|
||||
}
|
||||
// The formatter may also make dynamic adjustment to the options.
|
||||
if ($formatter instanceof OverrideOptionsInterface) {
|
||||
return $formatter->overrideOptions($structuredOutput, $options);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
|
||||
use Consolidation\OutputFormatters\Transformations\TableTransformation;
|
||||
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Comma-separated value formatters
|
||||
*
|
||||
* Display the provided structured data in a comma-separated list. If
|
||||
* there are multiple records provided, then they will be printed
|
||||
* one per line. The primary data types accepted are RowsOfFields and
|
||||
* PropertyList. The later behaves exactly like the former, save for
|
||||
* the fact that it contains but a single row. This formmatter can also
|
||||
* accept a PHP array; this is also interpreted as a single-row of data
|
||||
* with no header.
|
||||
*/
|
||||
class CsvFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
|
||||
{
|
||||
use ValidDataTypesTrait;
|
||||
use RenderTableDataTrait;
|
||||
|
||||
public function validDataTypes()
|
||||
{
|
||||
return
|
||||
[
|
||||
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'),
|
||||
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList'),
|
||||
new \ReflectionClass('\ArrayObject'),
|
||||
];
|
||||
}
|
||||
|
||||
public function validate($structuredData)
|
||||
{
|
||||
// If the provided data was of class RowsOfFields
|
||||
// or PropertyList, it will be converted into
|
||||
// a TableTransformation object.
|
||||
if (!is_array($structuredData) && (!$structuredData instanceof TableTransformation)) {
|
||||
throw new IncompatibleDataException(
|
||||
$this,
|
||||
$structuredData,
|
||||
$this->validDataTypes()
|
||||
);
|
||||
}
|
||||
// If the data was provided to us as a single array, then
|
||||
// convert it to a single row.
|
||||
if (is_array($structuredData) && !empty($structuredData)) {
|
||||
$firstRow = reset($structuredData);
|
||||
if (!is_array($firstRow)) {
|
||||
return [$structuredData];
|
||||
}
|
||||
}
|
||||
return $structuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default values for formatter options
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultFormatterOptions()
|
||||
{
|
||||
return [
|
||||
FormatterOptions::INCLUDE_FIELD_LABELS => true,
|
||||
FormatterOptions::DELIMITER => ',',
|
||||
FormatterOptions::CSV_ENCLOSURE => '"',
|
||||
FormatterOptions::CSV_ESCAPE_CHAR => "\\",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$defaults = $this->getDefaultFormatterOptions();
|
||||
|
||||
$includeFieldLabels = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults);
|
||||
if ($includeFieldLabels && ($data instanceof TableTransformation)) {
|
||||
$headers = $data->getHeaders();
|
||||
$this->writeOneLine($output, $headers, $options);
|
||||
}
|
||||
|
||||
foreach ($data as $line) {
|
||||
$this->writeOneLine($output, $line, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single a single line of formatted CSV data to the output stream.
|
||||
*
|
||||
* @param OutputInterface $output the output stream to write to.
|
||||
* @param array $data an array of field data to convert to a CSV string.
|
||||
* @param FormatterOptions $options the specified options for this formatter.
|
||||
*/
|
||||
protected function writeOneLine(OutputInterface $output, $data, $options)
|
||||
{
|
||||
$defaults = $this->getDefaultFormatterOptions();
|
||||
$delimiter = $options->get(FormatterOptions::DELIMITER, $defaults);
|
||||
$enclosure = $options->get(FormatterOptions::CSV_ENCLOSURE, $defaults);
|
||||
$escapeChar = $options->get(FormatterOptions::CSV_ESCAPE_CHAR, $defaults);
|
||||
$output->write($this->csvEscape($data, $delimiter, $enclosure, $escapeChar));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CSV-escaped string from an array of field data.
|
||||
*
|
||||
* @param array $data an array of field data to format as a CSV.
|
||||
* @param string $delimiter the delimiter to use between fields.
|
||||
* @param string $enclosure character to use when enclosing complex fields.
|
||||
* @param string $escapeChar character to use when escaping special characters.
|
||||
*
|
||||
* @return string|bool the formatted CSV string, or FALSE if the formatting failed.
|
||||
*/
|
||||
protected function csvEscape($data, $delimiter = ',', $enclosure = '"', $escapeChar = "\\")
|
||||
{
|
||||
$buffer = fopen('php://temp', 'r+');
|
||||
if (version_compare(PHP_VERSION, '5.5.4', '>=')) {
|
||||
fputcsv($buffer, $data, $delimiter, $enclosure, $escapeChar);
|
||||
} else {
|
||||
fputcsv($buffer, $data, $delimiter, $enclosure);
|
||||
}
|
||||
rewind($buffer);
|
||||
$csv = fgets($buffer);
|
||||
fclose($buffer);
|
||||
return $csv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
interface FormatterAwareInterface
|
||||
{
|
||||
public function setFormatter(FormatterInterface $formatter);
|
||||
public function getFormatter();
|
||||
public function isHumanReadable();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
trait FormatterAwareTrait
|
||||
{
|
||||
protected $formatter;
|
||||
|
||||
public function setFormatter(FormatterInterface $formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
public function getFormatter()
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
public function isHumanReadable()
|
||||
{
|
||||
return $this->formatter && $this->formatter instanceof \Consolidation\OutputFormatters\Formatters\HumanReadableFormat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Given structured data, apply appropriate
|
||||
* formatting, and return a printable string.
|
||||
*
|
||||
* @param OutputInterface output stream to write to
|
||||
* @param mixed $data Structured data to format
|
||||
* @param FormatterOptions formating options
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
/**
|
||||
* Marker interface that indicates that a cell data renderer
|
||||
* (@see Consolidation\OutputFormatters\SturcturedData\RenderCellInterface)
|
||||
* may test for to determine whether it is allowable to add
|
||||
* human-readable formatting into the cell data
|
||||
* (@see Consolidation\OutputFormatters\SturcturedData\NumericCallRenderer).
|
||||
*/
|
||||
interface HumanReadableFormat
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Json formatter
|
||||
*
|
||||
* Convert an array or ArrayObject into Json.
|
||||
*/
|
||||
class JsonFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$output->writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\RenderCellInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Display the data in a simple list.
|
||||
*
|
||||
* This formatter prints a plain, unadorned list of data,
|
||||
* with each data item appearing on a separate line. If you
|
||||
* wish your list to contain headers, then use the table
|
||||
* formatter, and wrap your data in an PropertyList.
|
||||
*/
|
||||
class ListFormatter implements FormatterInterface, OverrideRestructureInterface, RenderDataInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$output->writeln(implode("\n", $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function overrideRestructure($structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
// If the structured data implements ListDataInterface,
|
||||
// then we will render whatever data its 'getListData'
|
||||
// method provides.
|
||||
if ($structuredOutput instanceof ListDataInterface) {
|
||||
return $this->renderData($structuredOutput, $structuredOutput->getListData($options), $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function renderData($originalData, $restructuredData, FormatterOptions $options)
|
||||
{
|
||||
if ($originalData instanceof RenderCellInterface) {
|
||||
return $this->renderEachCell($originalData, $restructuredData, $options);
|
||||
}
|
||||
return $restructuredData;
|
||||
}
|
||||
|
||||
protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options)
|
||||
{
|
||||
foreach ($restructuredData as $key => $cellData) {
|
||||
$restructuredData[$key] = $originalData->renderCell($key, $cellData, $options, $restructuredData);
|
||||
}
|
||||
return $restructuredData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
interface MetadataFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Given some metadata, decide how to display it.
|
||||
*
|
||||
* @param OutputInterface output stream to write to
|
||||
* @param array $metadata associative array containing metadata
|
||||
* @param FormatterOptions formating options
|
||||
*/
|
||||
public function writeMetadata(OutputInterface $output, $metadata, FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\MetadataInterface;
|
||||
|
||||
trait MetadataFormatterTrait
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function writeMetadata(OutputInterface $output, $structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
$template = $options->get(FormatterOptions::METADATA_TEMPLATE);
|
||||
if (!$template) {
|
||||
return;
|
||||
}
|
||||
if (!$structuredOutput instanceof MetadataInterface) {
|
||||
return;
|
||||
}
|
||||
$metadata = $structuredOutput->getMetadata();
|
||||
if (empty($metadata)) {
|
||||
return;
|
||||
}
|
||||
$message = $this->interpolate($template, $metadata);
|
||||
return $output->writeln($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates context values into the message placeholders.
|
||||
*
|
||||
* @author PHP Framework Interoperability Group
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function interpolate($message, array $context)
|
||||
{
|
||||
// build a replacement array with braces around the context keys
|
||||
$replace = array();
|
||||
foreach ($context as $key => $val) {
|
||||
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
|
||||
$replace[sprintf('{%s}', $key)] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// interpolate replacement values into the message and return
|
||||
return strtr($message, $replace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Validate\ValidationInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* No output formatter
|
||||
*
|
||||
* This formatter never produces any output. It is useful in cases where
|
||||
* a command should not produce any output by default, but may do so if
|
||||
* the user explicitly includes a --format option.
|
||||
*/
|
||||
class NoOutputFormatter implements FormatterInterface, ValidationInterface
|
||||
{
|
||||
/**
|
||||
* All data types are acceptable.
|
||||
*/
|
||||
public function isValidDataType(\ReflectionClass $dataType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function validate($structuredData)
|
||||
{
|
||||
return $structuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Print_r formatter
|
||||
*
|
||||
* Run provided date thruogh print_r.
|
||||
*/
|
||||
class PrintRFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$output->writeln(print_r($data, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface RenderDataInterface
|
||||
{
|
||||
/**
|
||||
* Convert the contents of the output data just before it
|
||||
* is to be printed, prior to output but after restructuring
|
||||
* and validation.
|
||||
*
|
||||
* @param mixed $originalData
|
||||
* @param mixed $restructuredData
|
||||
* @param FormatterOptions $options Formatting options
|
||||
* @return mixed
|
||||
*/
|
||||
public function renderData($originalData, $restructuredData, FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\RenderCellInterface;
|
||||
|
||||
trait RenderTableDataTrait
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function renderData($originalData, $restructuredData, FormatterOptions $options)
|
||||
{
|
||||
if ($originalData instanceof RenderCellInterface) {
|
||||
return $this->renderEachCell($originalData, $restructuredData, $options);
|
||||
}
|
||||
return $restructuredData;
|
||||
}
|
||||
|
||||
protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options)
|
||||
{
|
||||
foreach ($restructuredData as $id => $row) {
|
||||
foreach ($row as $key => $cellData) {
|
||||
$restructuredData[$id][$key] = $originalData->renderCell($key, $cellData, $options, $row);
|
||||
}
|
||||
}
|
||||
return $restructuredData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
|
||||
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\ReorderFields;
|
||||
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
|
||||
use Consolidation\OutputFormatters\StructuredData\PropertyList;
|
||||
|
||||
/**
|
||||
* Display sections of data.
|
||||
*
|
||||
* This formatter takes data in the RowsOfFields data type.
|
||||
* Each row represents one section; the data in each section
|
||||
* is rendered in two columns, with the key in the first column
|
||||
* and the value in the second column.
|
||||
*/
|
||||
class SectionsFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
|
||||
{
|
||||
use ValidDataTypesTrait;
|
||||
use RenderTableDataTrait;
|
||||
|
||||
public function validDataTypes()
|
||||
{
|
||||
return
|
||||
[
|
||||
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function validate($structuredData)
|
||||
{
|
||||
// If the provided data was of class RowsOfFields
|
||||
// or PropertyList, it will be converted into
|
||||
// a TableTransformation object by the restructure call.
|
||||
if (!$structuredData instanceof TableDataInterface) {
|
||||
throw new IncompatibleDataException(
|
||||
$this,
|
||||
$structuredData,
|
||||
$this->validDataTypes()
|
||||
);
|
||||
}
|
||||
return $structuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options)
|
||||
{
|
||||
$table = new Table($output);
|
||||
$table->setStyle('compact');
|
||||
foreach ($tableTransformer as $rowid => $row) {
|
||||
$rowLabel = $tableTransformer->getRowLabel($rowid);
|
||||
$output->writeln('');
|
||||
$output->writeln($rowLabel);
|
||||
$sectionData = new PropertyList($row);
|
||||
$sectionOptions = new FormatterOptions([], $options->getOptions());
|
||||
$sectionTableTransformer = $sectionData->restructure($sectionOptions);
|
||||
$table->setRows($sectionTableTransformer->getTableData(true));
|
||||
$table->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Serialize formatter
|
||||
*
|
||||
* Run provided date thruogh serialize.
|
||||
*/
|
||||
class SerializeFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$output->writeln(serialize($data));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\UnstructuredInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\SimplifiedFormatterInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\StringTransformationInterface;
|
||||
use Consolidation\OutputFormatters\Validate\ValidationInterface;
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* String formatter
|
||||
*
|
||||
* This formatter is used as the default action when no
|
||||
* particular formatter is requested. It will print the
|
||||
* provided data only if it is a string; if any other
|
||||
* type is given, then nothing is printed.
|
||||
*/
|
||||
class StringFormatter implements FormatterInterface, ValidationInterface, OverrideOptionsInterface
|
||||
{
|
||||
/**
|
||||
* By default, we assume that we can convert any data type to `string`,
|
||||
* unless it implements UnstructuredInterface, in which case we won't
|
||||
* allow the `string` format unless the data type also implements
|
||||
* StringTransformationInterface.
|
||||
*/
|
||||
public function isValidDataType(\ReflectionClass $dataType)
|
||||
{
|
||||
if ($dataType->implementsInterface('\Consolidation\OutputFormatters\StructuredData\UnstructuredInterface') && !$dataType->implementsInterface('\Consolidation\OutputFormatters\Transformations\StringTransformationInterface')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
if (is_string($data)) {
|
||||
return $output->writeln($data);
|
||||
}
|
||||
return $this->reduceToSigleFieldAndWrite($output, $data, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function overrideOptions($structuredOutput, FormatterOptions $options)
|
||||
{
|
||||
$defaultField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD, [], '');
|
||||
$userFields = $options->get(FormatterOptions::FIELDS, [FormatterOptions::FIELDS => $options->get(FormatterOptions::FIELD)]);
|
||||
$optionsOverride = $options->override([]);
|
||||
if (empty($userFields) && !empty($defaultField)) {
|
||||
$optionsOverride->setOption(FormatterOptions::FIELDS, $defaultField);
|
||||
}
|
||||
return $optionsOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the data provided to a 'string' formatter is a table, then try
|
||||
* to emit it in a simplified form (by default, TSV).
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
* @param mixed $data
|
||||
* @param FormatterOptions $options
|
||||
*/
|
||||
protected function reduceToSigleFieldAndWrite(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
if ($data instanceof StringTransformationInterface) {
|
||||
$simplified = $data->simplifyToString($options);
|
||||
return $output->write($simplified);
|
||||
}
|
||||
|
||||
$alternateFormatter = new TsvFormatter();
|
||||
try {
|
||||
$data = $alternateFormatter->validate($data);
|
||||
$alternateFormatter->write($output, $data, $options);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always validate any data, though. This format will never
|
||||
* cause an error if it is selected for an incompatible data type; at
|
||||
* worse, it simply does not print any data.
|
||||
*/
|
||||
public function validate($structuredData)
|
||||
{
|
||||
return $structuredData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
|
||||
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\ReorderFields;
|
||||
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
|
||||
use Consolidation\OutputFormatters\Transformations\WordWrapper;
|
||||
use Consolidation\OutputFormatters\Formatters\HumanReadableFormat;
|
||||
|
||||
/**
|
||||
* Display a table of data with the Symfony Table class.
|
||||
*
|
||||
* This formatter takes data of either the RowsOfFields or
|
||||
* PropertyList data type. Tables can be rendered with the
|
||||
* rows running either vertically (the normal orientation) or
|
||||
* horizontally. By default, associative lists will be displayed
|
||||
* as two columns, with the key in the first column and the
|
||||
* value in the second column.
|
||||
*/
|
||||
class TableFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface, MetadataFormatterInterface, HumanReadableFormat
|
||||
{
|
||||
use ValidDataTypesTrait;
|
||||
use RenderTableDataTrait;
|
||||
use MetadataFormatterTrait;
|
||||
|
||||
protected $fieldLabels;
|
||||
protected $defaultFields;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function validDataTypes()
|
||||
{
|
||||
return
|
||||
[
|
||||
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'),
|
||||
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function validate($structuredData)
|
||||
{
|
||||
// If the provided data was of class RowsOfFields
|
||||
// or PropertyList, it will be converted into
|
||||
// a TableTransformation object by the restructure call.
|
||||
if (!$structuredData instanceof TableDataInterface) {
|
||||
throw new IncompatibleDataException(
|
||||
$this,
|
||||
$structuredData,
|
||||
$this->validDataTypes()
|
||||
);
|
||||
}
|
||||
return $structuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options)
|
||||
{
|
||||
$headers = [];
|
||||
$defaults = [
|
||||
FormatterOptions::TABLE_STYLE => 'consolidation',
|
||||
FormatterOptions::INCLUDE_FIELD_LABELS => true,
|
||||
];
|
||||
|
||||
$table = new Table($output);
|
||||
|
||||
static::addCustomTableStyles($table);
|
||||
|
||||
$table->setStyle($options->get(FormatterOptions::TABLE_STYLE, $defaults));
|
||||
$isList = $tableTransformer->isList();
|
||||
$includeHeaders = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults);
|
||||
$listDelimiter = $options->get(FormatterOptions::LIST_DELIMITER, $defaults);
|
||||
|
||||
$headers = $tableTransformer->getHeaders();
|
||||
$data = $tableTransformer->getTableData($includeHeaders && $isList);
|
||||
|
||||
if ($listDelimiter) {
|
||||
if (!empty($headers)) {
|
||||
array_splice($headers, 1, 0, ':');
|
||||
}
|
||||
$data = array_map(function ($item) {
|
||||
array_splice($item, 1, 0, ':');
|
||||
return $item;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
if ($includeHeaders && !$isList) {
|
||||
$table->setHeaders($headers);
|
||||
}
|
||||
|
||||
// todo: $output->getFormatter();
|
||||
$data = $this->wrap($headers, $data, $table->getStyle(), $options);
|
||||
$table->setRows($data);
|
||||
$table->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the table data
|
||||
* @param array $data
|
||||
* @param TableStyle $tableStyle
|
||||
* @param FormatterOptions $options
|
||||
* @return array
|
||||
*/
|
||||
protected function wrap($headers, $data, TableStyle $tableStyle, FormatterOptions $options)
|
||||
{
|
||||
$wrapper = new WordWrapper($options->get(FormatterOptions::TERMINAL_WIDTH));
|
||||
$wrapper->setPaddingFromStyle($tableStyle);
|
||||
if (!empty($headers)) {
|
||||
$headerLengths = array_map(function ($item) {
|
||||
return strlen($item);
|
||||
}, $headers);
|
||||
$wrapper->setMinimumWidths($headerLengths);
|
||||
}
|
||||
return $wrapper->wrap($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add our custom table style(s) to the table.
|
||||
*/
|
||||
protected static function addCustomTableStyles($table)
|
||||
{
|
||||
// The 'consolidation' style is the same as the 'symfony-style-guide'
|
||||
// style, except it maintains the colored headers used in 'default'.
|
||||
$consolidationStyle = new TableStyle();
|
||||
|
||||
if (method_exists($consolidationStyle, 'setHorizontalBorderChars')) {
|
||||
$consolidationStyle
|
||||
->setHorizontalBorderChars('-')
|
||||
->setVerticalBorderChars(' ')
|
||||
->setDefaultCrossingChar(' ')
|
||||
;
|
||||
} else {
|
||||
$consolidationStyle
|
||||
->setHorizontalBorderChar('-')
|
||||
->setVerticalBorderChar(' ')
|
||||
->setCrossingChar(' ')
|
||||
;
|
||||
}
|
||||
$table->setStyleDefinition('consolidation', $consolidationStyle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Transformations\TableTransformation;
|
||||
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Tab-separated value formatters
|
||||
*
|
||||
* Display the provided structured data in a tab-separated list. Output
|
||||
* escaping is much lighter, since there is no allowance for altering
|
||||
* the delimiter.
|
||||
*/
|
||||
class TsvFormatter extends CsvFormatter
|
||||
{
|
||||
protected function getDefaultFormatterOptions()
|
||||
{
|
||||
return [
|
||||
FormatterOptions::INCLUDE_FIELD_LABELS => false,
|
||||
];
|
||||
}
|
||||
|
||||
protected function writeOneLine(OutputInterface $output, $data, $options)
|
||||
{
|
||||
$output->writeln($this->tsvEscape($data));
|
||||
}
|
||||
|
||||
protected function tsvEscape($data)
|
||||
{
|
||||
return implode("\t", array_map(
|
||||
function ($item) {
|
||||
return str_replace(["\t", "\n"], ['\t', '\n'], $item);
|
||||
},
|
||||
$data
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
use Symfony\Component\VarDumper\Dumper\CliDumper;
|
||||
|
||||
/**
|
||||
* Var_dump formatter
|
||||
*
|
||||
* Run provided data through Symfony VarDumper component.
|
||||
*/
|
||||
class VarDumpFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$dumper = new CliDumper();
|
||||
$cloned_data = (new VarCloner())->cloneVar($data);
|
||||
|
||||
if ($output instanceof StreamOutput) {
|
||||
// When stream output is used the dumper is smart enough to
|
||||
// determine whether or not to apply colors to the dump.
|
||||
// @see Symfony\Component\VarDumper\Dumper\CliDumper::supportsColors
|
||||
$dumper->dump($cloned_data, $output->getStream());
|
||||
} else {
|
||||
// @todo Use dumper return value to get output once we stop support
|
||||
// VarDumper v2.
|
||||
$stream = fopen('php://memory', 'r+b');
|
||||
$dumper->dump($cloned_data, $stream);
|
||||
$output->writeln(stream_get_contents($stream, -1, 0));
|
||||
fclose($stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Var_export formatter
|
||||
*
|
||||
* Run provided date thruogh var_export.
|
||||
*/
|
||||
class VarExportFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
$output->writeln(var_export($data, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
|
||||
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\ReorderFields;
|
||||
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
|
||||
|
||||
/**
|
||||
* Display a table of data with the Symfony Table class.
|
||||
*
|
||||
* This formatter takes data of either the RowsOfFields or
|
||||
* PropertyList data type. Tables can be rendered with the
|
||||
* rows running either vertically (the normal orientation) or
|
||||
* horizontally. By default, associative lists will be displayed
|
||||
* as two columns, with the key in the first column and the
|
||||
* value in the second column.
|
||||
*/
|
||||
class XmlFormatter implements FormatterInterface, ValidDataTypesInterface
|
||||
{
|
||||
use ValidDataTypesTrait;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function validDataTypes()
|
||||
{
|
||||
return
|
||||
[
|
||||
new \ReflectionClass('\DOMDocument'),
|
||||
new \ReflectionClass('\ArrayObject'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function validate($structuredData)
|
||||
{
|
||||
if ($structuredData instanceof \DOMDocument) {
|
||||
return $structuredData;
|
||||
}
|
||||
if ($structuredData instanceof DomDataInterface) {
|
||||
return $structuredData->getDomData();
|
||||
}
|
||||
if ($structuredData instanceof \ArrayObject) {
|
||||
return $structuredData->getArrayCopy();
|
||||
}
|
||||
if (!is_array($structuredData)) {
|
||||
throw new IncompatibleDataException(
|
||||
$this,
|
||||
$structuredData,
|
||||
$this->validDataTypes()
|
||||
);
|
||||
}
|
||||
return $structuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $dom, FormatterOptions $options)
|
||||
{
|
||||
if (is_array($dom)) {
|
||||
$schema = $options->getXmlSchema();
|
||||
$dom = $schema->arrayToXML($dom);
|
||||
}
|
||||
$dom->formatOutput = true;
|
||||
$output->writeln($dom->saveXML());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Formatters;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Yaml formatter
|
||||
*
|
||||
* Convert an array or ArrayObject into Yaml.
|
||||
*/
|
||||
class YamlFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function write(OutputInterface $output, $data, FormatterOptions $options)
|
||||
{
|
||||
// Set Yaml\Dumper's default indentation for nested nodes/collections to
|
||||
// 2 spaces for consistency with Drupal coding standards.
|
||||
$indent = 2;
|
||||
// The level where you switch to inline YAML is set to PHP_INT_MAX to
|
||||
// ensure this does not occur.
|
||||
$output->writeln(Yaml::dump($data, PHP_INT_MAX, $indent, false, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Options;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\PropertyParser;
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchemaInterface;
|
||||
|
||||
/**
|
||||
* FormetterOptions holds information that affects the way a formatter
|
||||
* renders its output.
|
||||
*
|
||||
* There are three places where a formatter might get options from:
|
||||
*
|
||||
* 1. Configuration associated with the command that produced the output.
|
||||
* This is passed in to FormatterManager::write() along with the data
|
||||
* to format. It might originally come from annotations on the command,
|
||||
* or it might come from another source. Examples include the field labels
|
||||
* for a table, or the default list of fields to display.
|
||||
*
|
||||
* 2. Options specified by the user, e.g. by commandline options.
|
||||
*
|
||||
* 3. Default values associated with the formatter itself.
|
||||
*
|
||||
* This class caches configuration from sources (1) and (2), and expects
|
||||
* to be provided the defaults, (3), whenever a value is requested.
|
||||
*/
|
||||
class FormatterOptions
|
||||
{
|
||||
/** var array */
|
||||
protected $configurationData = [];
|
||||
/** var array */
|
||||
protected $options = [];
|
||||
/** var InputInterface */
|
||||
protected $input;
|
||||
|
||||
const FORMAT = 'format';
|
||||
const DEFAULT_FORMAT = 'default-format';
|
||||
const TABLE_STYLE = 'table-style';
|
||||
const LIST_ORIENTATION = 'list-orientation';
|
||||
const FIELDS = 'fields';
|
||||
const FIELD = 'field';
|
||||
const INCLUDE_FIELD_LABELS = 'include-field-labels';
|
||||
const ROW_LABELS = 'row-labels';
|
||||
const FIELD_LABELS = 'field-labels';
|
||||
const DEFAULT_FIELDS = 'default-fields';
|
||||
const DEFAULT_TABLE_FIELDS = 'default-table-fields';
|
||||
const DEFAULT_STRING_FIELD = 'default-string-field';
|
||||
const DELIMITER = 'delimiter';
|
||||
const CSV_ENCLOSURE = 'csv-enclosure';
|
||||
const CSV_ESCAPE_CHAR = 'csv-escape-char';
|
||||
const LIST_DELIMITER = 'list-delimiter';
|
||||
const TERMINAL_WIDTH = 'width';
|
||||
const METADATA_TEMPLATE = 'metadata-template';
|
||||
const HUMAN_READABLE = 'human-readable';
|
||||
|
||||
/**
|
||||
* Create a new FormatterOptions with the configuration data and the
|
||||
* user-specified options for this request.
|
||||
*
|
||||
* @see FormatterOptions::setInput()
|
||||
* @param array $configurationData
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($configurationData = [], $options = [])
|
||||
{
|
||||
$this->configurationData = $configurationData;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FormatterOptions object with new configuration data (provided),
|
||||
* and the same options data as this instance.
|
||||
*
|
||||
* @param array $configurationData
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
public function override($configurationData)
|
||||
{
|
||||
$override = new self();
|
||||
$override
|
||||
->setConfigurationData($configurationData + $this->getConfigurationData())
|
||||
->setOptions($this->getOptions());
|
||||
return $override;
|
||||
}
|
||||
|
||||
public function setTableStyle($style)
|
||||
{
|
||||
return $this->setConfigurationValue(self::TABLE_STYLE, $style);
|
||||
}
|
||||
|
||||
public function setDelimiter($delimiter)
|
||||
{
|
||||
return $this->setConfigurationValue(self::DELIMITER, $delimiter);
|
||||
}
|
||||
|
||||
public function setCsvEnclosure($enclosure)
|
||||
{
|
||||
return $this->setConfigurationValue(self::CSV_ENCLOSURE, $enclosure);
|
||||
}
|
||||
|
||||
public function setCsvEscapeChar($escapeChar)
|
||||
{
|
||||
return $this->setConfigurationValue(self::CSV_ESCAPE_CHAR, $escapeChar);
|
||||
}
|
||||
|
||||
public function setListDelimiter($listDelimiter)
|
||||
{
|
||||
return $this->setConfigurationValue(self::LIST_DELIMITER, $listDelimiter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setIncludeFieldLables($includFieldLables)
|
||||
{
|
||||
return $this->setConfigurationValue(self::INCLUDE_FIELD_LABELS, $includFieldLables);
|
||||
}
|
||||
|
||||
public function setListOrientation($listOrientation)
|
||||
{
|
||||
return $this->setConfigurationValue(self::LIST_ORIENTATION, $listOrientation);
|
||||
}
|
||||
|
||||
public function setRowLabels($rowLabels)
|
||||
{
|
||||
return $this->setConfigurationValue(self::ROW_LABELS, $rowLabels);
|
||||
}
|
||||
|
||||
public function setDefaultFields($fields)
|
||||
{
|
||||
return $this->setConfigurationValue(self::DEFAULT_FIELDS, $fields);
|
||||
}
|
||||
|
||||
public function setFieldLabels($fieldLabels)
|
||||
{
|
||||
return $this->setConfigurationValue(self::FIELD_LABELS, $fieldLabels);
|
||||
}
|
||||
|
||||
public function setDefaultStringField($defaultStringField)
|
||||
{
|
||||
return $this->setConfigurationValue(self::DEFAULT_STRING_FIELD, $defaultStringField);
|
||||
}
|
||||
|
||||
public function setWidth($width)
|
||||
{
|
||||
return $this->setConfigurationValue(self::TERMINAL_WIDTH, $width);
|
||||
}
|
||||
|
||||
public function setHumanReadable($isHumanReadable = true)
|
||||
{
|
||||
return $this->setConfigurationValue(self::HUMAN_READABLE, $isHumanReadable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatter option
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $defaults
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key, $defaults = [], $default = false)
|
||||
{
|
||||
$value = $this->fetch($key, $defaults, $default);
|
||||
return $this->parse($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the XmlSchema to use with --format=xml for data types that support
|
||||
* that. This is used when an array needs to be converted into xml.
|
||||
*
|
||||
* @return XmlSchema
|
||||
*/
|
||||
public function getXmlSchema()
|
||||
{
|
||||
return new XmlSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the format that was requested by the caller.
|
||||
*
|
||||
* @param array $defaults
|
||||
* @return string
|
||||
*/
|
||||
public function getFormat($defaults = [])
|
||||
{
|
||||
return $this->get(self::FORMAT, [], $this->get(self::DEFAULT_FORMAT, $defaults, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a key, and return its raw value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $defaults
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetch($key, $defaults = [], $default = false)
|
||||
{
|
||||
$defaults = $this->defaultsForKey($key, $defaults, $default);
|
||||
$values = $this->fetchRawValues($defaults);
|
||||
return $values[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce provided defaults to the single item identified by '$key',
|
||||
* if it exists, or an empty array otherwise.
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $defaults
|
||||
* @return array
|
||||
*/
|
||||
protected function defaultsForKey($key, $defaults, $default = false)
|
||||
{
|
||||
if (array_key_exists($key, $defaults)) {
|
||||
return [$key => $defaults[$key]];
|
||||
}
|
||||
return [$key => $default];
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up all of the items associated with the provided defaults.
|
||||
*
|
||||
* @param array $defaults
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchRawValues($defaults = [])
|
||||
{
|
||||
return array_merge(
|
||||
$defaults,
|
||||
$this->getConfigurationData(),
|
||||
$this->getOptions(),
|
||||
$this->getInputOptions($defaults)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the raw value for a specific key, do any type conversion
|
||||
* (e.g. from a textual list to an array) needed for the data.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function parse($key, $value)
|
||||
{
|
||||
$optionFormat = $this->getOptionFormat($key);
|
||||
if (!empty($optionFormat) && is_string($value)) {
|
||||
return $this->$optionFormat($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a textual list to an array
|
||||
*
|
||||
* @param string $value
|
||||
* @return array
|
||||
*/
|
||||
public function parsePropertyList($value)
|
||||
{
|
||||
return PropertyParser::parse($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a specific key, return the class method name of the
|
||||
* parsing method for data stored under this key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function getOptionFormat($key)
|
||||
{
|
||||
$propertyFormats = [
|
||||
self::ROW_LABELS => 'PropertyList',
|
||||
self::FIELD_LABELS => 'PropertyList',
|
||||
];
|
||||
if (array_key_exists($key, $propertyFormats)) {
|
||||
return "parse{$propertyFormats[$key]}";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the configuration data for this formatter options object.
|
||||
*
|
||||
* @param array $configurationData
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
public function setConfigurationData($configurationData)
|
||||
{
|
||||
$this->configurationData = $configurationData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change one configuration value for this formatter option.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return FormetterOptions
|
||||
*/
|
||||
protected function setConfigurationValue($key, $value)
|
||||
{
|
||||
$this->configurationData[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change one configuration value for this formatter option, but only
|
||||
* if it does not already have a value set.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return FormetterOptions
|
||||
*/
|
||||
public function setConfigurationDefault($key, $value)
|
||||
{
|
||||
if (!array_key_exists($key, $this->configurationData)) {
|
||||
return $this->setConfigurationValue($key, $value);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the configuration data for this object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigurationData()
|
||||
{
|
||||
return $this->configurationData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all of the options that were specified by the user for this request.
|
||||
*
|
||||
* @param array $options
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->options = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change one option value specified by the user for this request.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
public function setOption($key, $value)
|
||||
{
|
||||
$this->options[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the user-specified options for this request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a Symfony Console InputInterface containing the user-specified
|
||||
* options for this request.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @return type
|
||||
*/
|
||||
public function setInput(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the options from the provided $defaults array that
|
||||
* exist in our InputInterface object.
|
||||
*
|
||||
* @param array $defaults
|
||||
* @return array
|
||||
*/
|
||||
public function getInputOptions($defaults)
|
||||
{
|
||||
if (!isset($this->input)) {
|
||||
return [];
|
||||
}
|
||||
$options = [];
|
||||
foreach ($defaults as $key => $value) {
|
||||
if ($this->input->hasOption($key)) {
|
||||
$result = $this->input->getOption($key);
|
||||
if (isset($result)) {
|
||||
$options[$key] = $this->input->getOption($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Options;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface OverrideOptionsInterface
|
||||
{
|
||||
/**
|
||||
* Allow the formatter to mess with the configuration options before any
|
||||
* transformations et. al. get underway.
|
||||
*
|
||||
* @param mixed $structuredOutput Data to restructure
|
||||
* @param FormatterOptions $options Formatting options
|
||||
* @return FormatterOptions
|
||||
*/
|
||||
public function overrideOptions($structuredOutput, FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Transformations\ReorderFields;
|
||||
|
||||
/**
|
||||
* Base class for all list data types.
|
||||
*/
|
||||
class AbstractListData extends \ArrayObject implements ListDataInterface
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
public function getListData(FormatterOptions $options)
|
||||
{
|
||||
return array_keys($this->getArrayCopy());
|
||||
}
|
||||
|
||||
protected function getReorderedFieldLabels($data, $options, $defaults)
|
||||
{
|
||||
$reorderer = new ReorderFields();
|
||||
$fieldLabels = $reorderer->reorder(
|
||||
$this->getFields($options, $defaults),
|
||||
$options->get(FormatterOptions::FIELD_LABELS, $defaults),
|
||||
$data
|
||||
);
|
||||
return $fieldLabels;
|
||||
}
|
||||
|
||||
protected function getFields($options, $defaults)
|
||||
{
|
||||
$fieldShortcut = $options->get(FormatterOptions::FIELD);
|
||||
if (!empty($fieldShortcut)) {
|
||||
return [$fieldShortcut];
|
||||
}
|
||||
$result = $options->get(FormatterOptions::FIELDS);
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
$isHumanReadable = $options->get(FormatterOptions::HUMAN_READABLE);
|
||||
if ($isHumanReadable) {
|
||||
$result = $options->get(FormatterOptions::DEFAULT_TABLE_FIELDS);
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return $options->get(FormatterOptions::DEFAULT_FIELDS, $defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* A structured list may provide its own set of default options. These
|
||||
* will be used in place of the command's default options (from the
|
||||
* annotations) in instances where the user does not provide the options
|
||||
* explicitly (on the commandline) or implicitly (via a configuration file).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function defaultOptions()
|
||||
{
|
||||
return [
|
||||
FormatterOptions::FIELDS => [],
|
||||
FormatterOptions::FIELD_LABELS => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\TableTransformation;
|
||||
|
||||
/**
|
||||
* Holds an array where each element of the array is one row,
|
||||
* and each row contains an associative array where the keys
|
||||
* are the field names, and the values are the field data.
|
||||
*
|
||||
* It is presumed that every row contains the same keys.
|
||||
*/
|
||||
abstract class AbstractStructuredList extends AbstractListData implements RestructureInterface, RenderCellCollectionInterface
|
||||
{
|
||||
use RenderCellCollectionTrait;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
abstract public function restructure(FormatterOptions $options);
|
||||
|
||||
protected function createTableTransformation($data, $options)
|
||||
{
|
||||
$defaults = $this->defaultOptions();
|
||||
$fieldLabels = $this->getReorderedFieldLabels($data, $options, $defaults);
|
||||
|
||||
$tableTransformer = $this->instantiateTableTransformation($data, $fieldLabels, $options->get(FormatterOptions::ROW_LABELS, $defaults));
|
||||
if ($options->get(FormatterOptions::LIST_ORIENTATION, $defaults)) {
|
||||
$tableTransformer->setLayout(TableTransformation::LIST_LAYOUT);
|
||||
}
|
||||
|
||||
return $tableTransformer;
|
||||
}
|
||||
|
||||
protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels)
|
||||
{
|
||||
return new TableTransformation($data, $fieldLabels, $rowLabels);
|
||||
}
|
||||
|
||||
protected function defaultOptions()
|
||||
{
|
||||
return [
|
||||
FormatterOptions::ROW_LABELS => [],
|
||||
FormatterOptions::DEFAULT_FIELDS => [],
|
||||
] + parent::defaultOptions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
/**
|
||||
* Old name for PropertyList class.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class AssociativeList extends PropertyList
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
class CallableRenderer implements RenderCellInterface
|
||||
{
|
||||
/** @var callable */
|
||||
protected $renderFunction;
|
||||
|
||||
public function __construct(callable $renderFunction)
|
||||
{
|
||||
$this->renderFunction = $renderFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
|
||||
{
|
||||
return call_user_func($this->renderFunction, $key, $cellData, $options, $rowData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface ConversionInterface
|
||||
{
|
||||
/**
|
||||
* Allow structured data to be converted -- i.e. from
|
||||
* RowsOfFields to UnstructuredListData.
|
||||
*
|
||||
* @param FormatterOptions $options Formatting options
|
||||
*/
|
||||
public function convert(FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\UnstructuredDataListTransformation;
|
||||
|
||||
/**
|
||||
* FieldProcessor will do various alterations on field sets.
|
||||
*/
|
||||
class FieldProcessor
|
||||
{
|
||||
public static function processFieldAliases($fields)
|
||||
{
|
||||
if (!is_array($fields)) {
|
||||
$fields = array_filter(explode(',', $fields));
|
||||
}
|
||||
$transformed_fields = [];
|
||||
foreach ($fields as $field) {
|
||||
list($machine_name,$label) = explode(' as ', $field) + [$field, preg_replace('#.*\.#', '', $field)];
|
||||
$transformed_fields[$machine_name] = $label;
|
||||
}
|
||||
return $transformed_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the data structure has unstructured field access,
|
||||
* e.g. `a.b.c` or `foo as bar`.
|
||||
* @param type $fields
|
||||
* @return type
|
||||
*/
|
||||
public static function hasUnstructuredFieldAccess($fields)
|
||||
{
|
||||
if (is_array($fields)) {
|
||||
$fields = implode(',', $fields);
|
||||
}
|
||||
return (strpos($fields, ' as ') !== false) || (strpos($fields, '.') !== false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
|
||||
|
||||
class HelpDocument implements DomDataInterface
|
||||
{
|
||||
/**
|
||||
* Convert data into a \DomDocument.
|
||||
*
|
||||
* @return \DomDocument
|
||||
*/
|
||||
public function getDomData()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
/**
|
||||
* @deprecated Use UnstructuredListData
|
||||
*/
|
||||
class ListDataFromKeys extends AbstractListData
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface ListDataInterface
|
||||
{
|
||||
/**
|
||||
* Convert data to a format suitable for use in a list.
|
||||
* By default, the array values will be used. Implement
|
||||
* ListDataInterface to use some other criteria (e.g. array keys).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getListData(FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface MetadataHolderInterface
|
||||
{
|
||||
public function getDataKey();
|
||||
public function setDataKey($key);
|
||||
public function getMetadataKey();
|
||||
public function setMetadataKey($key);
|
||||
public function extractData($data);
|
||||
public function extractMetadata($data);
|
||||
public function reconstruct($data, $metadata);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
/**
|
||||
* A structured data object may contains some elements that
|
||||
* are actually metadata. Metadata is not included in the
|
||||
* output of tabular data formatters (e.g. table, csv), although
|
||||
* some of these (e.g. table) may render the metadata alongside
|
||||
* the data. Raw data formatters (e.g. yaml, json) will render
|
||||
* both the data and the metadata.
|
||||
*
|
||||
* There are two possible options for the data format; either the
|
||||
* data is nested inside some element, and ever other item is
|
||||
* metadata, or the metadata may be nested inside some element,
|
||||
* and every other item is the data rows.
|
||||
*
|
||||
* Example 1: nested data
|
||||
*
|
||||
* [
|
||||
* 'data' => [ ... rows of field data ... ],
|
||||
* 'metadata1' => '...',
|
||||
* 'metadata2' => '...',
|
||||
* ]
|
||||
*
|
||||
* Example 2: nested metadata
|
||||
*
|
||||
* [
|
||||
* 'metadata' => [ ... metadata items ... ],
|
||||
* 'rowid1' => [ ... ],
|
||||
* 'rowid2' => [ ... ],
|
||||
* ]
|
||||
*
|
||||
* It is, of course, also possible that both the data and
|
||||
* the metadata may be nested inside subelements.
|
||||
*/
|
||||
trait MetadataHolderTrait
|
||||
{
|
||||
protected $dataKey = false;
|
||||
protected $metadataKey = false;
|
||||
|
||||
public function getDataKey()
|
||||
{
|
||||
return $this->dataKey;
|
||||
}
|
||||
|
||||
public function setDataKey($key)
|
||||
{
|
||||
$this->dataKey = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMetadataKey()
|
||||
{
|
||||
return $this->metadataKey;
|
||||
}
|
||||
|
||||
public function setMetadataKey($key)
|
||||
{
|
||||
$this->metadataKey = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extractData($data)
|
||||
{
|
||||
if ($this->metadataKey) {
|
||||
unset($data[$this->metadataKey]);
|
||||
}
|
||||
if ($this->dataKey) {
|
||||
if (!isset($data[$this->dataKey])) {
|
||||
return [];
|
||||
}
|
||||
return $data[$this->dataKey];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function extractMetadata($data)
|
||||
{
|
||||
if (!$this->dataKey && !$this->metadataKey) {
|
||||
return [];
|
||||
}
|
||||
if ($this->dataKey) {
|
||||
unset($data[$this->dataKey]);
|
||||
}
|
||||
if ($this->metadataKey) {
|
||||
if (!isset($data[$this->metadataKey])) {
|
||||
return [];
|
||||
}
|
||||
return $data[$this->metadataKey];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function reconstruct($data, $metadata)
|
||||
{
|
||||
$reconstructedData = ($this->dataKey) ? [$this->dataKey => $data] : $data;
|
||||
$reconstructedMetadata = ($this->metadataKey) ? [$this->metadataKey => $metadata] : $metadata;
|
||||
|
||||
return $reconstructedData + $reconstructedMetadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface MetadataInterface
|
||||
{
|
||||
/**
|
||||
* Return the metadata associated with the structured data (if any)
|
||||
*/
|
||||
public function getMetadata();
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterAwareTrait;
|
||||
|
||||
/**
|
||||
* Create a formatter to add commas to numeric data.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* -------
|
||||
* Value
|
||||
* -------
|
||||
* 2,384
|
||||
* 143,894
|
||||
* 23
|
||||
* 98,538
|
||||
*
|
||||
* This formatter may also be re-used for other purposes where right-justified
|
||||
* data is desired by simply making a subclass. See method comments below.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* return (new RowsOfFields($data))->addRenderer(
|
||||
* new NumericCellRenderer($data, ['value'])
|
||||
* );
|
||||
*
|
||||
*/
|
||||
class NumericCellRenderer implements RenderCellInterface, FormatterAwareInterface
|
||||
{
|
||||
use FormatterAwareTrait;
|
||||
|
||||
protected $data;
|
||||
protected $renderedColumns;
|
||||
protected $widths = [];
|
||||
|
||||
/**
|
||||
* NumericCellRenderer constructor
|
||||
*/
|
||||
public function __construct($data, $renderedColumns)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->renderedColumns = $renderedColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
|
||||
{
|
||||
if (!$this->isRenderedFormat($options) || !$this->isRenderedColumn($key)) {
|
||||
return $cellData;
|
||||
}
|
||||
if ($this->isRenderedData($cellData)) {
|
||||
$cellData = $this->formatCellData($cellData);
|
||||
}
|
||||
return $this->justifyCellData($key, $cellData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Right-justify the cell data.
|
||||
*/
|
||||
protected function justifyCellData($key, $cellData)
|
||||
{
|
||||
return str_pad($cellData, $this->columnWidth($key), " ", STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this format is to be formatted.
|
||||
*/
|
||||
protected function isRenderedFormat(FormatterOptions $options)
|
||||
{
|
||||
return $this->isHumanReadable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a column that should be formatted.
|
||||
*/
|
||||
protected function isRenderedColumn($key)
|
||||
{
|
||||
return array_key_exists($key, $this->renderedColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore cell data that should not be formatted.
|
||||
*/
|
||||
protected function isRenderedData($cellData)
|
||||
{
|
||||
return is_numeric($cellData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the cell data.
|
||||
*/
|
||||
protected function formatCellData($cellData)
|
||||
{
|
||||
return number_format($this->convertCellDataToString($cellData));
|
||||
}
|
||||
|
||||
/**
|
||||
* This formatter only works with columns whose columns are strings.
|
||||
* To use this formatter for another purpose, override this method
|
||||
* to ensure that the cell data is a string before it is formatted.
|
||||
*/
|
||||
protected function convertCellDataToString($cellData)
|
||||
{
|
||||
return $cellData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached column width for the provided key.
|
||||
*/
|
||||
protected function columnWidth($key)
|
||||
{
|
||||
if (!isset($this->widths[$key])) {
|
||||
$this->widths[$key] = $this->calculateColumnWidth($key);
|
||||
}
|
||||
return $this->widths[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the cached table data, calculate the largest width
|
||||
* for the data in the table for use when right-justifying.
|
||||
*/
|
||||
protected function calculateColumnWidth($key)
|
||||
{
|
||||
$width = isset($this->renderedColumns[$key]) ? $this->renderedColumns[$key] : 0;
|
||||
foreach ($this->data as $row) {
|
||||
$data = $this->formatCellData($row[$key]);
|
||||
$width = max(strlen($data), $width);
|
||||
}
|
||||
return $width;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
interface OriginalDataInterface
|
||||
{
|
||||
/**
|
||||
* Return the original data for this table. Used by any
|
||||
* formatter that expects an array.
|
||||
*/
|
||||
public function getOriginalData();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\PropertyParser;
|
||||
use Consolidation\OutputFormatters\Transformations\ReorderFields;
|
||||
use Consolidation\OutputFormatters\Transformations\TableTransformation;
|
||||
use Consolidation\OutputFormatters\Transformations\PropertyListTableTransformation;
|
||||
|
||||
/**
|
||||
* Holds an array where each element of the array is one
|
||||
* key : value pair. The keys must be unique, as is typically
|
||||
* the case for associative arrays.
|
||||
*/
|
||||
class PropertyList extends AbstractStructuredList implements ConversionInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function convert(FormatterOptions $options)
|
||||
{
|
||||
$defaults = $this->defaultOptions();
|
||||
$fields = $this->getFields($options, $defaults);
|
||||
if (FieldProcessor::hasUnstructuredFieldAccess($fields)) {
|
||||
return new UnstructuredData($this->getArrayCopy());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restructure this data for output by converting it into a table
|
||||
* transformation object.
|
||||
*
|
||||
* @param FormatterOptions $options Options that affect output formatting.
|
||||
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
|
||||
*/
|
||||
public function restructure(FormatterOptions $options)
|
||||
{
|
||||
$data = [$this->getArrayCopy()];
|
||||
$options->setConfigurationDefault('list-orientation', true);
|
||||
$tableTransformer = $this->createTableTransformation($data, $options);
|
||||
return $tableTransformer;
|
||||
}
|
||||
|
||||
public function getListData(FormatterOptions $options)
|
||||
{
|
||||
$data = $this->getArrayCopy();
|
||||
|
||||
$defaults = $this->defaultOptions();
|
||||
$fieldLabels = $this->getReorderedFieldLabels([$data], $options, $defaults);
|
||||
|
||||
$result = [];
|
||||
foreach ($fieldLabels as $id => $label) {
|
||||
$result[$id] = $data[$id];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function defaultOptions()
|
||||
{
|
||||
return [
|
||||
FormatterOptions::LIST_ORIENTATION => true,
|
||||
] + parent::defaultOptions();
|
||||
}
|
||||
|
||||
protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels)
|
||||
{
|
||||
return new PropertyListTableTransformation($data, $fieldLabels, $rowLabels);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
|
||||
|
||||
interface RenderCellCollectionInterface extends RenderCellInterface, FormatterAwareInterface
|
||||
{
|
||||
const PRIORITY_FIRST = 'first';
|
||||
const PRIORITY_NORMAL = 'normal';
|
||||
const PRIORITY_FALLBACK = 'fallback';
|
||||
|
||||
/**
|
||||
* Add a renderer
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRenderer(RenderCellInterface $renderer);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
|
||||
use Consolidation\OutputFormatters\Formatters\FormatterAwareTrait;
|
||||
|
||||
trait RenderCellCollectionTrait
|
||||
{
|
||||
use FormatterAwareTrait;
|
||||
|
||||
/** @var RenderCellInterface[] */
|
||||
protected $rendererList = [
|
||||
RenderCellCollectionInterface::PRIORITY_FIRST => [],
|
||||
RenderCellCollectionInterface::PRIORITY_NORMAL => [],
|
||||
RenderCellCollectionInterface::PRIORITY_FALLBACK => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Add a renderer
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRenderer(RenderCellInterface $renderer, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL)
|
||||
{
|
||||
$this->rendererList[$priority][] = $renderer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callable as a renderer
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRendererFunction(callable $rendererFn, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL)
|
||||
{
|
||||
$renderer = new CallableRenderer($rendererFn);
|
||||
return $this->addRenderer($renderer, $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
|
||||
{
|
||||
$flattenedRendererList = array_reduce(
|
||||
$this->rendererList,
|
||||
function ($carry, $item) {
|
||||
return array_merge($carry, $item);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
foreach ($flattenedRendererList as $renderer) {
|
||||
if ($renderer instanceof FormatterAwareInterface) {
|
||||
$renderer->setFormatter($this->getFormatter());
|
||||
}
|
||||
$cellData = $renderer->renderCell($key, $cellData, $options, $rowData);
|
||||
}
|
||||
return $cellData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface RenderCellInterface
|
||||
{
|
||||
/**
|
||||
* Convert the contents of one table cell into a string,
|
||||
* so that it may be placed in the table. Renderer should
|
||||
* return the $cellData passed to it if it does not wish to
|
||||
* process it.
|
||||
*
|
||||
* @param string $key Identifier of the cell being rendered
|
||||
* @param mixed $cellData The data to render
|
||||
* @param FormatterOptions $options The formatting options
|
||||
* @param array $rowData The rest of the row data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function renderCell($key, $cellData, FormatterOptions $options, $rowData);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface RestructureInterface
|
||||
{
|
||||
/**
|
||||
* Allow structured data to be restructured -- i.e. to select fields
|
||||
* to show, reorder fields, etc.
|
||||
*
|
||||
* @param FormatterOptions $options Formatting options
|
||||
*/
|
||||
public function restructure(FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
/**
|
||||
* Holds an array where each element of the array is one row,
|
||||
* and each row contains an associative array where the keys
|
||||
* are the field names, and the values are the field data.
|
||||
*
|
||||
* It is presumed that every row contains the same keys.
|
||||
*/
|
||||
class RowsOfFields extends AbstractStructuredList implements ConversionInterface
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function convert(FormatterOptions $options)
|
||||
{
|
||||
$defaults = $this->defaultOptions();
|
||||
$fields = $this->getFields($options, $defaults);
|
||||
if (FieldProcessor::hasUnstructuredFieldAccess($fields)) {
|
||||
return new UnstructuredListData($this->getArrayCopy());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restructure this data for output by converting it into a table
|
||||
* transformation object.
|
||||
*
|
||||
* @param FormatterOptions $options Options that affect output formatting.
|
||||
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
|
||||
*/
|
||||
public function restructure(FormatterOptions $options)
|
||||
{
|
||||
$data = $this->getArrayCopy();
|
||||
return $this->createTableTransformation($data, $options);
|
||||
}
|
||||
|
||||
public function getListData(FormatterOptions $options)
|
||||
{
|
||||
return array_keys($this->getArrayCopy());
|
||||
}
|
||||
|
||||
protected function defaultOptions()
|
||||
{
|
||||
return [
|
||||
FormatterOptions::LIST_ORIENTATION => false,
|
||||
] + parent::defaultOptions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
/**
|
||||
* A RowsOfFields data structure that also contains metadata.
|
||||
* @see MetadataHolderTrait
|
||||
*/
|
||||
class RowsOfFieldsWithMetadata extends RowsOfFields implements MetadataInterface, MetadataHolderInterface
|
||||
{
|
||||
use MetadataHolderTrait;
|
||||
|
||||
/**
|
||||
* Restructure this data for output by converting it into a table
|
||||
* transformation object. First, though, remove any metadata items.
|
||||
*
|
||||
* @param FormatterOptions $options Options that affect output formatting.
|
||||
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
|
||||
*/
|
||||
public function restructure(FormatterOptions $options)
|
||||
{
|
||||
$originalData = $this->getArrayCopy();
|
||||
$data = $this->extractData($originalData);
|
||||
$tableTranformer = $this->createTableTransformation($data, $options);
|
||||
$tableTranformer->setOriginalData($this);
|
||||
return $tableTranformer;
|
||||
}
|
||||
|
||||
public function getMetadata()
|
||||
{
|
||||
return $this->extractMetadata($this->getArrayCopy());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
interface TableDataInterface
|
||||
{
|
||||
/**
|
||||
* Convert structured data into a form suitable for use
|
||||
* by the table formatter.
|
||||
*
|
||||
* @param boolean $includeRowKey Add a field containing the
|
||||
* key from each row.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTableData($includeRowKey = false);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\UnstructuredDataTransformation;
|
||||
|
||||
/**
|
||||
* Represents aribtrary unstructured array data where the
|
||||
* data to display in --list format comes from the array keys.
|
||||
*
|
||||
* Unstructured list data can have variable keys in every rown (unlike
|
||||
* RowsOfFields, which expects uniform rows), and the data elements may
|
||||
* themselves be deep arrays.
|
||||
*/
|
||||
class UnstructuredData extends AbstractListData implements UnstructuredInterface, RestructureInterface
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
public function restructure(FormatterOptions $options)
|
||||
{
|
||||
$defaults = $this->defaultOptions();
|
||||
$fields = $this->getFields($options, $defaults);
|
||||
|
||||
return new UnstructuredDataTransformation($this->getArrayCopy(), FieldProcessor::processFieldAliases($fields));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
/**
|
||||
* UnstructuredInterface is a marker interface that indicates that the
|
||||
* data type is unstructured, and has no default conversion to a string.
|
||||
* Unstructured data supports the `string` format only if it also implements
|
||||
* StringTransformationInterface.
|
||||
*/
|
||||
interface UnstructuredInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
|
||||
use Consolidation\OutputFormatters\Transformations\UnstructuredDataListTransformation;
|
||||
|
||||
/**
|
||||
* Represents aribtrary unstructured array data where the
|
||||
* data to display in --list format comes from the array keys.
|
||||
*
|
||||
* Unstructured list data can have variable keys in every rown (unlike
|
||||
* RowsOfFields, which expects uniform rows), and the data elements may
|
||||
* themselves be deep arrays.
|
||||
*/
|
||||
class UnstructuredListData extends AbstractListData implements UnstructuredInterface, RestructureInterface
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
public function restructure(FormatterOptions $options)
|
||||
{
|
||||
$defaults = $this->defaultOptions();
|
||||
$fields = $this->getFields($options, $defaults);
|
||||
|
||||
return new UnstructuredDataListTransformation($this->getArrayCopy(), FieldProcessor::processFieldAliases($fields));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\StructuredData\Xml;
|
||||
|
||||
interface DomDataInterface
|
||||
{
|
||||
/**
|
||||
* Convert data into a \DomDocument.
|
||||
*
|
||||
* @return \DomDocument
|
||||
*/
|
||||
public function getDomData();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Consolidation\OutputFormatters\StructuredData\Xml;
|
||||
|
||||
class XmlSchema implements XmlSchemaInterface
|
||||
{
|
||||
protected $elementList;
|
||||
|
||||
public function __construct($elementList = [])
|
||||
{
|
||||
$defaultElementList =
|
||||
[
|
||||
'*' => ['description'],
|
||||
];
|
||||
$this->elementList = array_merge_recursive($elementList, $defaultElementList);
|
||||
}
|
||||
|
||||
public function arrayToXML($structuredData)
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$topLevelElement = $this->getTopLevelElementName($structuredData);
|
||||
$this->addXmlData($dom, $dom, $topLevelElement, $structuredData);
|
||||
return $dom;
|
||||
}
|
||||
|
||||
protected function addXmlData(\DOMDocument $dom, $xmlParent, $elementName, $structuredData)
|
||||
{
|
||||
$element = $dom->createElement($elementName);
|
||||
$xmlParent->appendChild($element);
|
||||
if (is_string($structuredData)) {
|
||||
$element->appendChild($dom->createTextNode($structuredData));
|
||||
return;
|
||||
}
|
||||
$this->addXmlChildren($dom, $element, $elementName, $structuredData);
|
||||
}
|
||||
|
||||
protected function addXmlChildren(\DOMDocument $dom, $xmlParent, $elementName, $structuredData)
|
||||
{
|
||||
foreach ($structuredData as $key => $value) {
|
||||
$this->addXmlDataOrAttribute($dom, $xmlParent, $elementName, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addXmlDataOrAttribute(\DOMDocument $dom, $xmlParent, $elementName, $key, $value)
|
||||
{
|
||||
$childElementName = $this->getDefaultElementName($elementName);
|
||||
$elementName = $this->determineElementName($key, $childElementName, $value);
|
||||
if (($elementName != $childElementName) && $this->isAttribute($elementName, $key, $value)) {
|
||||
$xmlParent->setAttribute($key, $value);
|
||||
return;
|
||||
}
|
||||
$this->addXmlData($dom, $xmlParent, $elementName, $value);
|
||||
}
|
||||
|
||||
protected function determineElementName($key, $childElementName, $value)
|
||||
{
|
||||
if (is_numeric($key)) {
|
||||
return $childElementName;
|
||||
}
|
||||
if (is_object($value)) {
|
||||
$value = (array)$value;
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
return $key;
|
||||
}
|
||||
if (array_key_exists('id', $value) && ($value['id'] == $key)) {
|
||||
return $childElementName;
|
||||
}
|
||||
if (array_key_exists('name', $value) && ($value['name'] == $key)) {
|
||||
return $childElementName;
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
protected function getTopLevelElementName($structuredData)
|
||||
{
|
||||
return 'document';
|
||||
}
|
||||
|
||||
protected function getDefaultElementName($parentElementName)
|
||||
{
|
||||
$singularName = $this->singularForm($parentElementName);
|
||||
if (isset($singularName)) {
|
||||
return $singularName;
|
||||
}
|
||||
return 'item';
|
||||
}
|
||||
|
||||
protected function isAttribute($parentElementName, $elementName, $value)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
return !$this->inElementList($parentElementName, $elementName) && !$this->inElementList('*', $elementName);
|
||||
}
|
||||
|
||||
protected function inElementList($parentElementName, $elementName)
|
||||
{
|
||||
if (!array_key_exists($parentElementName, $this->elementList)) {
|
||||
return false;
|
||||
}
|
||||
return in_array($elementName, $this->elementList[$parentElementName]);
|
||||
}
|
||||
|
||||
protected function singularForm($name)
|
||||
{
|
||||
if (substr($name, strlen($name) - 1) == "s") {
|
||||
return substr($name, 0, strlen($name) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected function isAssoc($data)
|
||||
{
|
||||
return array_keys($data) == range(0, count($data));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Consolidation\OutputFormatters\StructuredData\Xml;
|
||||
|
||||
/**
|
||||
* When using arrays, we could represent XML data in a number of
|
||||
* different ways.
|
||||
*
|
||||
* For example, given the following XML data strucutre:
|
||||
*
|
||||
* <document id="1" name="doc">
|
||||
* <foobars>
|
||||
* <foobar id="123">
|
||||
* <name>blah</name>
|
||||
* <widgets>
|
||||
* <widget>
|
||||
* <foo>a</foo>
|
||||
* <bar>b</bar>
|
||||
* <baz>c</baz>
|
||||
* </widget>
|
||||
* </widgets>
|
||||
* </foobar>
|
||||
* </foobars>
|
||||
* </document>
|
||||
*
|
||||
* This could be:
|
||||
*
|
||||
* [
|
||||
* 'id' => 1,
|
||||
* 'name' => 'doc',
|
||||
* 'foobars' =>
|
||||
* [
|
||||
* [
|
||||
* 'id' => '123',
|
||||
* 'name' => 'blah',
|
||||
* 'widgets' =>
|
||||
* [
|
||||
* [
|
||||
* 'foo' => 'a',
|
||||
* 'bar' => 'b',
|
||||
* 'baz' => 'c',
|
||||
* ]
|
||||
* ],
|
||||
* ],
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* The challenge is more in going from an array back to the more
|
||||
* structured xml format. Note that any given key => string mapping
|
||||
* could represent either an attribute, or a simple XML element
|
||||
* containing only a string value. In general, we do *not* want to add
|
||||
* extra layers of nesting in the data structure to disambiguate between
|
||||
* these kinds of data, as we want the source data to render cleanly
|
||||
* into other formats, e.g. yaml, json, et. al., and we do not want to
|
||||
* force every data provider to have to consider the optimal xml schema
|
||||
* for their data.
|
||||
*
|
||||
* Our strategy, therefore, is to expect clients that wish to provide
|
||||
* a very specific xml representation to return a DOMDocument, and,
|
||||
* for other data structures where xml is a secondary concern, then we
|
||||
* will use some default heuristics to convert from arrays to xml.
|
||||
*/
|
||||
interface XmlSchemaInterface
|
||||
{
|
||||
/**
|
||||
* Convert data to a format suitable for use in a list.
|
||||
* By default, the array values will be used. Implement
|
||||
* ListDataInterface to use some other criteria (e.g. array keys).
|
||||
*
|
||||
* @return \DOMDocument
|
||||
*/
|
||||
public function arrayToXml($structuredData);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
|
||||
|
||||
/**
|
||||
* Simplify a DOMDocument to an array.
|
||||
*/
|
||||
class DomToArraySimplifier implements SimplifyToArrayInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass $dataType
|
||||
*/
|
||||
public function canSimplify(\ReflectionClass $dataType)
|
||||
{
|
||||
return
|
||||
$dataType->isSubclassOf('\Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface') ||
|
||||
$dataType->isSubclassOf('DOMDocument') ||
|
||||
($dataType->getName() == 'DOMDocument');
|
||||
}
|
||||
|
||||
public function simplifyToArray($structuredData, FormatterOptions $options)
|
||||
{
|
||||
if ($structuredData instanceof DomDataInterface) {
|
||||
$structuredData = $structuredData->getDomData();
|
||||
}
|
||||
if ($structuredData instanceof \DOMDocument) {
|
||||
// $schema = $options->getXmlSchema();
|
||||
$simplified = $this->elementToArray($structuredData);
|
||||
$structuredData = array_shift($simplified);
|
||||
}
|
||||
return $structuredData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively convert the provided DOM element into a php array.
|
||||
*
|
||||
* @param \DOMNode $element
|
||||
* @return array
|
||||
*/
|
||||
protected function elementToArray(\DOMNode $element)
|
||||
{
|
||||
if ($element->nodeType == XML_TEXT_NODE) {
|
||||
return $element->nodeValue;
|
||||
}
|
||||
$attributes = $this->getNodeAttributes($element);
|
||||
$children = $this->getNodeChildren($element);
|
||||
|
||||
return array_merge($attributes, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the attributes of the provided element.
|
||||
*
|
||||
* @param \DOMNode $element
|
||||
* @return array
|
||||
*/
|
||||
protected function getNodeAttributes($element)
|
||||
{
|
||||
if (empty($element->attributes)) {
|
||||
return [];
|
||||
}
|
||||
$attributes = [];
|
||||
foreach ($element->attributes as $key => $attribute) {
|
||||
$attributes[$key] = $attribute->nodeValue;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the children of the provided element, with simplification.
|
||||
*
|
||||
* @param \DOMNode $element
|
||||
* @return array
|
||||
*/
|
||||
protected function getNodeChildren($element)
|
||||
{
|
||||
if (empty($element->childNodes)) {
|
||||
return [];
|
||||
}
|
||||
$uniformChildrenName = $this->hasUniformChildren($element);
|
||||
// Check for plurals.
|
||||
if (in_array($element->nodeName, ["{$uniformChildrenName}s", "{$uniformChildrenName}es"])) {
|
||||
$result = $this->getUniformChildren($element->nodeName, $element);
|
||||
} else {
|
||||
$result = $this->getUniqueChildren($element->nodeName, $element);
|
||||
}
|
||||
return array_filter($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data from the children of the provided node in preliminary
|
||||
* form.
|
||||
*
|
||||
* @param \DOMNode $element
|
||||
* @return array
|
||||
*/
|
||||
protected function getNodeChildrenData($element)
|
||||
{
|
||||
$children = [];
|
||||
foreach ($element->childNodes as $key => $value) {
|
||||
$children[$key] = $this->elementToArray($value);
|
||||
}
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the children of the provided element are uniform.
|
||||
* @see getUniformChildren(), below.
|
||||
*
|
||||
* @param \DOMNode $element
|
||||
* @return boolean
|
||||
*/
|
||||
protected function hasUniformChildren($element)
|
||||
{
|
||||
$last = false;
|
||||
foreach ($element->childNodes as $key => $value) {
|
||||
$name = $value->nodeName;
|
||||
if (!$name) {
|
||||
return false;
|
||||
}
|
||||
if ($last && ($name != $last)) {
|
||||
return false;
|
||||
}
|
||||
$last = $name;
|
||||
}
|
||||
return $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the children of the provided DOM element into an array.
|
||||
* Here, 'uniform' means that all of the element names of the children
|
||||
* are identical, and further, the element name of the parent is the
|
||||
* plural form of the child names. When the children are uniform in
|
||||
* this way, then the parent element name will be used as the key to
|
||||
* store the children in, and the child list will be returned as a
|
||||
* simple list with their (duplicate) element names omitted.
|
||||
*
|
||||
* @param string $parentKey
|
||||
* @param \DOMNode $element
|
||||
* @return array
|
||||
*/
|
||||
protected function getUniformChildren($parentKey, $element)
|
||||
{
|
||||
$children = $this->getNodeChildrenData($element);
|
||||
$simplifiedChildren = [];
|
||||
foreach ($children as $key => $value) {
|
||||
if ($this->valueCanBeSimplified($value)) {
|
||||
$value = array_shift($value);
|
||||
}
|
||||
$id = $this->getIdOfValue($value);
|
||||
if ($id) {
|
||||
$simplifiedChildren[$parentKey][$id] = $value;
|
||||
} else {
|
||||
$simplifiedChildren[$parentKey][] = $value;
|
||||
}
|
||||
}
|
||||
return $simplifiedChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the provided value has additional unnecessary
|
||||
* nesting. {"color": "red"} is converted to "red". No other
|
||||
* simplification is done.
|
||||
*
|
||||
* @param \DOMNode $value
|
||||
* @return boolean
|
||||
*/
|
||||
protected function valueCanBeSimplified($value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
if (count($value) != 1) {
|
||||
return false;
|
||||
}
|
||||
$data = array_shift($value);
|
||||
return is_string($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the object has an 'id' or 'name' element, then use that
|
||||
* as the array key when storing this value in its parent.
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
protected function getIdOfValue($value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('id', $value)) {
|
||||
return trim($value['id'], '-');
|
||||
}
|
||||
if (array_key_exists('name', $value)) {
|
||||
return trim($value['name'], '-');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the children of the provided DOM element into an array.
|
||||
* Here, 'unique' means that all of the element names of the children are
|
||||
* different. Since the element names will become the key of the
|
||||
* associative array that is returned, so duplicates are not supported.
|
||||
* If there are any duplicates, then an exception will be thrown.
|
||||
*
|
||||
* @param string $parentKey
|
||||
* @param \DOMNode $element
|
||||
* @return array
|
||||
*/
|
||||
protected function getUniqueChildren($parentKey, $element)
|
||||
{
|
||||
$children = $this->getNodeChildrenData($element);
|
||||
if ((count($children) == 1) && (is_string($children[0]))) {
|
||||
return [$element->nodeName => $children[0]];
|
||||
}
|
||||
$simplifiedChildren = [];
|
||||
foreach ($children as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && (count($value) == 1)) {
|
||||
$valueKeys = array_keys($value);
|
||||
$key = $valueKeys[0];
|
||||
$value = array_shift($value);
|
||||
}
|
||||
if (array_key_exists($key, $simplifiedChildren)) {
|
||||
throw new \Exception("Cannot convert data from a DOM document to an array, because <$key> appears more than once, and is not wrapped in a <{$key}s> element.");
|
||||
}
|
||||
$simplifiedChildren[$key] = $value;
|
||||
}
|
||||
return $simplifiedChildren;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface OverrideRestructureInterface
|
||||
{
|
||||
/**
|
||||
* Select data to use directly from the structured output,
|
||||
* before the restructure operation has been executed.
|
||||
*
|
||||
* @param mixed $structuredOutput Data to restructure
|
||||
* @param FormatterOptions $options Formatting options
|
||||
* @return mixed
|
||||
*/
|
||||
public function overrideRestructure($structuredOutput, FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
class PropertyListTableTransformation extends TableTransformation
|
||||
{
|
||||
public function getOriginalData()
|
||||
{
|
||||
$data = $this->getArrayCopy();
|
||||
return $data[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
/**
|
||||
* Transform a string of properties into a PHP associative array.
|
||||
*
|
||||
* Input:
|
||||
*
|
||||
* one: red
|
||||
* two: white
|
||||
* three: blue
|
||||
*
|
||||
* Output:
|
||||
*
|
||||
* [
|
||||
* 'one' => 'red',
|
||||
* 'two' => 'white',
|
||||
* 'three' => 'blue',
|
||||
* ]
|
||||
*/
|
||||
class PropertyParser
|
||||
{
|
||||
public static function parse($data)
|
||||
{
|
||||
if (!is_string($data)) {
|
||||
return $data;
|
||||
}
|
||||
$result = [];
|
||||
$lines = explode("\n", $data);
|
||||
foreach ($lines as $line) {
|
||||
list($key, $value) = explode(':', trim($line), 2) + ['', ''];
|
||||
if (!empty($key) && !empty($value)) {
|
||||
$result[$key] = trim($value);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Symfony\Component\Finder\Glob;
|
||||
use Consolidation\OutputFormatters\Exception\UnknownFieldException;
|
||||
|
||||
/**
|
||||
* Reorder the field labels based on the user-selected fields
|
||||
* to display.
|
||||
*/
|
||||
class ReorderFields
|
||||
{
|
||||
/**
|
||||
* Given a simple list of user-supplied field keys or field labels,
|
||||
* return a reordered version of the field labels matching the
|
||||
* user selection.
|
||||
*
|
||||
* @param string|array $fields The user-selected fields
|
||||
* @param array $fieldLabels An associative array mapping the field
|
||||
* key to the field label
|
||||
* @param array $data The data that will be rendered.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function reorder($fields, $fieldLabels, $data)
|
||||
{
|
||||
$firstRow = reset($data);
|
||||
if (!$firstRow) {
|
||||
$firstRow = $fieldLabels;
|
||||
}
|
||||
if (empty($fieldLabels) && !empty($data)) {
|
||||
$fieldLabels = array_combine(array_keys($firstRow), array_map('ucfirst', array_keys($firstRow)));
|
||||
}
|
||||
$fields = $this->getSelectedFieldKeys($fields, $fieldLabels);
|
||||
if (empty($fields)) {
|
||||
return array_intersect_key($fieldLabels, $firstRow);
|
||||
}
|
||||
return $this->reorderFieldLabels($fields, $fieldLabels, $data);
|
||||
}
|
||||
|
||||
protected function reorderFieldLabels($fields, $fieldLabels, $data)
|
||||
{
|
||||
$result = [];
|
||||
$firstRow = reset($data);
|
||||
if (!$firstRow) {
|
||||
$firstRow = $fieldLabels;
|
||||
}
|
||||
foreach ($fields as $field) {
|
||||
if (array_key_exists($field, $firstRow)) {
|
||||
if (array_key_exists($field, $fieldLabels)) {
|
||||
$result[$field] = $fieldLabels[$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getSelectedFieldKeys($fields, $fieldLabels)
|
||||
{
|
||||
if (empty($fieldLabels)) {
|
||||
return [];
|
||||
}
|
||||
if (is_string($fields)) {
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
$selectedFields = [];
|
||||
foreach ($fields as $field) {
|
||||
$matchedFields = $this->matchFieldInLabelMap($field, $fieldLabels);
|
||||
if (empty($matchedFields)) {
|
||||
throw new UnknownFieldException($field);
|
||||
}
|
||||
$selectedFields = array_merge($selectedFields, $matchedFields);
|
||||
}
|
||||
return $selectedFields;
|
||||
}
|
||||
|
||||
protected function matchFieldInLabelMap($field, $fieldLabels)
|
||||
{
|
||||
$fieldRegex = $this->convertToRegex($field);
|
||||
return
|
||||
array_filter(
|
||||
array_keys($fieldLabels),
|
||||
function ($key) use ($fieldRegex, $fieldLabels) {
|
||||
$value = $fieldLabels[$key];
|
||||
return preg_match($fieldRegex, $value) || preg_match($fieldRegex, $key);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the provided string into a regex suitable for use in
|
||||
* preg_match.
|
||||
*
|
||||
* Matching occurs in the same way as the Symfony Finder component:
|
||||
* http://symfony.com/doc/current/components/finder.html#file-name
|
||||
*/
|
||||
protected function convertToRegex($str)
|
||||
{
|
||||
return $this->isRegex($str) ? $str : Glob::toRegex($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the string is a regex. This function is copied from
|
||||
* MultiplePcreFilterIterator in the Symfony Finder component.
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return bool Whether the given string is a regex
|
||||
*/
|
||||
protected function isRegex($str)
|
||||
{
|
||||
if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
|
||||
$start = substr($m[1], 0, 1);
|
||||
$end = substr($m[1], -1);
|
||||
|
||||
if ($start === $end) {
|
||||
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
|
||||
}
|
||||
|
||||
foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) {
|
||||
if ($start === $delimiters[0] && $end === $delimiters[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface SimplifyToArrayInterface
|
||||
{
|
||||
/**
|
||||
* Convert structured data into a generic array, usable by generic
|
||||
* array-based formatters. Objects that implement this interface may
|
||||
* be attached to the FormatterManager, and will be used on any data
|
||||
* structure that needs to be simplified into an array. An array
|
||||
* simplifier should take no action other than to return its input data
|
||||
* if it cannot simplify the provided data into an array.
|
||||
*
|
||||
* @param mixed $structuredOutput The data to simplify to an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function simplifyToArray($structuredOutput, FormatterOptions $options);
|
||||
|
||||
/**
|
||||
* Indicate whether or not the given data type can be simplified to an array
|
||||
*/
|
||||
public function canSimplify(\ReflectionClass $structuredOutput);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
interface StringTransformationInterface
|
||||
{
|
||||
/**
|
||||
* simplifyToString is called by the string formatter to convert
|
||||
* structured data to a simple string.
|
||||
*/
|
||||
public function simplifyToString(FormatterOptions $options);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
|
||||
use Consolidation\OutputFormatters\StructuredData\MetadataHolderInterface;
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
use Consolidation\OutputFormatters\Formatters\TsvFormatter;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
|
||||
class TableTransformation extends \ArrayObject implements TableDataInterface, StringTransformationInterface, OriginalDataInterface
|
||||
{
|
||||
protected $headers;
|
||||
protected $rowLabels;
|
||||
protected $layout;
|
||||
/** @var MetadataHolderInterface */
|
||||
protected $originalData;
|
||||
|
||||
const TABLE_LAYOUT = 'table';
|
||||
const LIST_LAYOUT = 'list';
|
||||
|
||||
public function __construct($data, $fieldLabels, $rowLabels = [])
|
||||
{
|
||||
$this->headers = $fieldLabels;
|
||||
$this->rowLabels = $rowLabels;
|
||||
$rows = static::transformRows($data, $fieldLabels);
|
||||
$this->layout = self::TABLE_LAYOUT;
|
||||
parent::__construct($rows);
|
||||
}
|
||||
|
||||
public function setLayout($layout)
|
||||
{
|
||||
$this->layout = $layout;
|
||||
}
|
||||
|
||||
public function getLayout()
|
||||
{
|
||||
return $this->layout;
|
||||
}
|
||||
|
||||
public function isList()
|
||||
{
|
||||
return $this->layout == self::LIST_LAYOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function simplifyToString(FormatterOptions $options)
|
||||
{
|
||||
$alternateFormatter = new TsvFormatter();
|
||||
$output = new BufferedOutput();
|
||||
|
||||
try {
|
||||
$data = $alternateFormatter->validate($this->getArrayCopy());
|
||||
$alternateFormatter->write($output, $this->getArrayCopy(), $options);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
return $output->fetch();
|
||||
}
|
||||
|
||||
protected static function transformRows($data, $fieldLabels)
|
||||
{
|
||||
$rows = [];
|
||||
foreach ($data as $rowid => $row) {
|
||||
$rows[$rowid] = static::transformRow($row, $fieldLabels);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
protected static function transformRow($row, $fieldLabels)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($fieldLabels as $key => $label) {
|
||||
$result[$key] = array_key_exists($key, $row) ? $row[$key] : '';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getHeader($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->headers)) {
|
||||
return $this->headers[$key];
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
public function getRowLabels()
|
||||
{
|
||||
return $this->rowLabels;
|
||||
}
|
||||
|
||||
public function getRowLabel($rowid)
|
||||
{
|
||||
if (array_key_exists($rowid, $this->rowLabels)) {
|
||||
return $this->rowLabels[$rowid];
|
||||
}
|
||||
return $rowid;
|
||||
}
|
||||
|
||||
public function getOriginalData()
|
||||
{
|
||||
if (isset($this->originalData)) {
|
||||
return $this->originalData->reconstruct($this->getArrayCopy(), $this->originalData->getMetadata());
|
||||
}
|
||||
return $this->getArrayCopy();
|
||||
}
|
||||
|
||||
public function setOriginalData(MetadataHolderInterface $data)
|
||||
{
|
||||
$this->originalData = $data;
|
||||
}
|
||||
|
||||
public function getTableData($includeRowKey = false)
|
||||
{
|
||||
$data = $this->getArrayCopy();
|
||||
if ($this->isList()) {
|
||||
$data = $this->convertTableToList();
|
||||
}
|
||||
if ($includeRowKey) {
|
||||
$data = $this->getRowDataWithKey($data);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function convertTableToList()
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this as $row) {
|
||||
foreach ($row as $key => $value) {
|
||||
$result[$key][] = $value;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getRowDataWithKey($data)
|
||||
{
|
||||
$result = [];
|
||||
$i = 0;
|
||||
foreach ($data as $key => $row) {
|
||||
array_unshift($row, $this->getHeader($key));
|
||||
$i++;
|
||||
$result[$key] = $row;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Dflydev\DotAccessData\Data;
|
||||
use Dflydev\DotAccessData\Exception\MissingPathException;
|
||||
|
||||
class UnstructuredDataFieldAccessor
|
||||
{
|
||||
protected $data;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function get($fields)
|
||||
{
|
||||
$data = new Data($this->data);
|
||||
$result = new Data();
|
||||
foreach ($fields as $key => $label) {
|
||||
$item = null;
|
||||
try {
|
||||
$item = $data->get($key);
|
||||
} catch (MissingPathException $e) {
|
||||
}
|
||||
if (isset($item)) {
|
||||
if ($label == '.') {
|
||||
if (!is_array($item)) {
|
||||
return $item;
|
||||
}
|
||||
foreach ($item as $key => $value) {
|
||||
$result->set($key, $value);
|
||||
}
|
||||
} else {
|
||||
$result->set($label, $data->get($key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result->export();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
class UnstructuredDataListTransformation extends \ArrayObject implements StringTransformationInterface
|
||||
{
|
||||
public function __construct($data, $fields)
|
||||
{
|
||||
$this->originalData = $data;
|
||||
$rows = static::transformRows($data, $fields);
|
||||
parent::__construct($rows);
|
||||
}
|
||||
|
||||
protected static function transformRows($data, $fields)
|
||||
{
|
||||
$rows = [];
|
||||
foreach ($data as $rowid => $row) {
|
||||
$rows[$rowid] = UnstructuredDataTransformation::transformRow($row, $fields);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function simplifyToString(FormatterOptions $options)
|
||||
{
|
||||
$result = '';
|
||||
$iterator = $this->getIterator();
|
||||
while ($iterator->valid()) {
|
||||
$simplifiedRow = UnstructuredDataTransformation::simplifyRow($iterator->current());
|
||||
if (isset($simplifiedRow)) {
|
||||
$result .= "$simplifiedRow\n";
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Options\FormatterOptions;
|
||||
|
||||
class UnstructuredDataTransformation extends \ArrayObject implements StringTransformationInterface
|
||||
{
|
||||
protected $originalData;
|
||||
|
||||
public function __construct($data, $fields)
|
||||
{
|
||||
$this->originalData = $data;
|
||||
$rows = static::transformRow($data, $fields);
|
||||
parent::__construct($rows);
|
||||
}
|
||||
|
||||
public function simplifyToString(FormatterOptions $options)
|
||||
{
|
||||
return static::simplifyRow($this->getArrayCopy());
|
||||
}
|
||||
|
||||
public static function transformRow($row, $fields)
|
||||
{
|
||||
if (empty($fields)) {
|
||||
return $row;
|
||||
}
|
||||
$fieldAccessor = new UnstructuredDataFieldAccessor($row);
|
||||
return $fieldAccessor->get($fields);
|
||||
}
|
||||
|
||||
public static function simplifyRow($row)
|
||||
{
|
||||
if (is_string($row)) {
|
||||
return $row;
|
||||
}
|
||||
if (static::isSimpleArray($row)) {
|
||||
return implode("\n", $row);
|
||||
}
|
||||
// No good way to simplify - just dump a json fragment
|
||||
return json_encode($row);
|
||||
}
|
||||
|
||||
protected static function isSimpleArray($row)
|
||||
{
|
||||
foreach ($row as $item) {
|
||||
if (!is_string($item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations;
|
||||
|
||||
use Consolidation\OutputFormatters\Transformations\Wrap\CalculateWidths;
|
||||
use Consolidation\OutputFormatters\Transformations\Wrap\ColumnWidths;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
class WordWrapper
|
||||
{
|
||||
protected $width;
|
||||
protected $minimumWidths;
|
||||
|
||||
// For now, hardcode these to match what the Symfony Table helper does.
|
||||
// Note that these might actually need to be adjusted depending on the
|
||||
// table style.
|
||||
protected $extraPaddingAtBeginningOfLine = 0;
|
||||
protected $extraPaddingAtEndOfLine = 0;
|
||||
protected $paddingInEachCell = 3;
|
||||
|
||||
public function __construct($width)
|
||||
{
|
||||
$this->width = $width;
|
||||
$this->minimumWidths = new ColumnWidths();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate our padding widths from the specified table style.
|
||||
* @param TableStyle $style
|
||||
*/
|
||||
public function setPaddingFromStyle(TableStyle $style)
|
||||
{
|
||||
if (method_exists($style, 'getBorderChars')) {
|
||||
return $this->setPaddingFromSymfony5Style($style);
|
||||
}
|
||||
|
||||
$verticalBorderLen = strlen(sprintf($style->getBorderFormat(), $style->getVerticalBorderChar()));
|
||||
$paddingLen = strlen($style->getPaddingChar());
|
||||
|
||||
$this->extraPaddingAtBeginningOfLine = 0;
|
||||
$this->extraPaddingAtEndOfLine = $verticalBorderLen;
|
||||
$this->paddingInEachCell = $verticalBorderLen + $paddingLen + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate our padding widths from the specified table style.
|
||||
* @param TableStyle $style
|
||||
*/
|
||||
public function setPaddingFromSymfony5Style(TableStyle $style)
|
||||
{
|
||||
$borderChars = $style->getBorderChars();
|
||||
$verticalBorderChar = $borderChars[1];
|
||||
$verticalBorderLen = strlen(sprintf($style->getBorderFormat(), $verticalBorderChar));
|
||||
$paddingLen = strlen($style->getPaddingChar());
|
||||
|
||||
$this->extraPaddingAtBeginningOfLine = 0;
|
||||
$this->extraPaddingAtEndOfLine = $verticalBorderLen;
|
||||
$this->paddingInEachCell = $verticalBorderLen + $paddingLen + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* If columns have minimum widths, then set them here.
|
||||
* @param array $minimumWidths
|
||||
*/
|
||||
public function setMinimumWidths($minimumWidths)
|
||||
{
|
||||
$this->minimumWidths = new ColumnWidths($minimumWidths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum width of just one column
|
||||
*/
|
||||
public function minimumWidth($colkey, $width)
|
||||
{
|
||||
$this->minimumWidths->setWidth($colkey, $width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the cells in each part of the provided data table
|
||||
* @param array $rows
|
||||
* @return array
|
||||
*/
|
||||
public function wrap($rows, $widths = [])
|
||||
{
|
||||
$auto_widths = $this->calculateWidths($rows, $widths);
|
||||
|
||||
// If no widths were provided, then disable wrapping
|
||||
if ($auto_widths->isEmpty()) {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// Do wordwrap on all cells.
|
||||
$newrows = array();
|
||||
foreach ($rows as $rowkey => $row) {
|
||||
foreach ($row as $colkey => $cell) {
|
||||
$newrows[$rowkey][$colkey] = $this->wrapCell($cell, $auto_widths->width($colkey));
|
||||
}
|
||||
}
|
||||
|
||||
return $newrows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine what widths we'll use for wrapping.
|
||||
*/
|
||||
protected function calculateWidths($rows, $widths = [])
|
||||
{
|
||||
// Widths must be provided in some form or another, or we won't wrap.
|
||||
if (empty($widths) && !$this->width) {
|
||||
return new ColumnWidths();
|
||||
}
|
||||
|
||||
// Technically, `$widths`, if provided here, should be used
|
||||
// as the exact widths to wrap to. For now we'll just treat
|
||||
// these as minimum widths
|
||||
$minimumWidths = $this->minimumWidths->combine(new ColumnWidths($widths));
|
||||
|
||||
$calculator = new CalculateWidths();
|
||||
$dataCellWidths = $calculator->calculateLongestCell($rows);
|
||||
|
||||
$availableWidth = $this->width - $dataCellWidths->paddingSpace($this->paddingInEachCell, $this->extraPaddingAtEndOfLine, $this->extraPaddingAtBeginningOfLine);
|
||||
|
||||
$this->minimumWidths->adjustMinimumWidths($availableWidth, $dataCellWidths);
|
||||
|
||||
return $calculator->calculate($availableWidth, $dataCellWidths, $minimumWidths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap one cell. Guard against modifying non-strings and
|
||||
* then call through to wordwrap().
|
||||
*
|
||||
* @param mixed $cell
|
||||
* @param string $cellWidth
|
||||
* @return mixed
|
||||
*/
|
||||
protected function wrapCell($cell, $cellWidth)
|
||||
{
|
||||
if (!is_string($cell)) {
|
||||
return $cell;
|
||||
}
|
||||
return wordwrap($cell, $cellWidth, "\n", true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations\Wrap;
|
||||
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
/**
|
||||
* Calculate column widths for table cells.
|
||||
*
|
||||
* Influenced by Drush and webmozart/console.
|
||||
*/
|
||||
class CalculateWidths
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the total amount of available space, and the width of
|
||||
* the columns to place, calculate the optimum column widths to use.
|
||||
*/
|
||||
public function calculate($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
|
||||
{
|
||||
// First, check to see if all columns will fit at their full widths.
|
||||
// If so, do no further calculations. (This may be redundant with
|
||||
// the short column width calculation.)
|
||||
if ($dataWidths->totalWidth() <= $availableWidth) {
|
||||
return $dataWidths->enforceMinimums($minimumWidths);
|
||||
}
|
||||
|
||||
// Get the short columns first. If there are none, then distribute all
|
||||
// of the available width among the remaining columns.
|
||||
$shortColWidths = $this->getShortColumns($availableWidth, $dataWidths, $minimumWidths);
|
||||
if ($shortColWidths->isEmpty()) {
|
||||
return $this->distributeLongColumns($availableWidth, $dataWidths, $minimumWidths);
|
||||
}
|
||||
|
||||
// If some short columns were removed, then account for the length
|
||||
// of the removed columns and make a recursive call (since the average
|
||||
// width may be higher now, if the removed columns were shorter in
|
||||
// length than the previous average).
|
||||
$availableWidth -= $shortColWidths->totalWidth();
|
||||
$remainingWidths = $dataWidths->removeColumns($shortColWidths->keys());
|
||||
$remainingColWidths = $this->calculate($availableWidth, $remainingWidths, $minimumWidths);
|
||||
|
||||
return $shortColWidths->combine($remainingColWidths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the longest cell data from any row of each of the cells.
|
||||
*/
|
||||
public function calculateLongestCell($rows)
|
||||
{
|
||||
return $this->calculateColumnWidths(
|
||||
$rows,
|
||||
function ($cell) {
|
||||
return strlen($cell);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the longest word and longest line in the provided data.
|
||||
*/
|
||||
public function calculateLongestWord($rows)
|
||||
{
|
||||
return $this->calculateColumnWidths(
|
||||
$rows,
|
||||
function ($cell) {
|
||||
return static::longestWordLength($cell);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function calculateColumnWidths($rows, callable $fn)
|
||||
{
|
||||
$widths = [];
|
||||
|
||||
// Examine each row and find the longest line length and longest
|
||||
// word in each column.
|
||||
foreach ($rows as $rowkey => $row) {
|
||||
foreach ($row as $colkey => $cell) {
|
||||
$value = $fn($cell);
|
||||
if ((!isset($widths[$colkey]) || ($widths[$colkey] < $value))) {
|
||||
$widths[$colkey] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ColumnWidths($widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the columns whose longest line length is less than or
|
||||
* equal to the average width.
|
||||
*/
|
||||
public function getShortColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
|
||||
{
|
||||
$averageWidth = $dataWidths->averageWidth($availableWidth);
|
||||
$shortColWidths = $dataWidths->findShortColumns($averageWidth);
|
||||
return $shortColWidths->enforceMinimums($minimumWidths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute the remainig space among the columns that were not
|
||||
* included in the list of "short" columns.
|
||||
*/
|
||||
public function distributeLongColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
|
||||
{
|
||||
// First distribute the remainder without regard to the minimum widths.
|
||||
$result = $dataWidths->distribute($availableWidth);
|
||||
|
||||
// Find columns that are shorter than their minimum width.
|
||||
$undersized = $result->findUndersizedColumns($minimumWidths);
|
||||
|
||||
// Nothing too small? Great, we're done!
|
||||
if ($undersized->isEmpty()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Take out the columns that are too small and redistribute the rest.
|
||||
$availableWidth -= $undersized->totalWidth();
|
||||
$remaining = $dataWidths->removeColumns($undersized->keys());
|
||||
$distributeRemaining = $this->distributeLongColumns($availableWidth, $remaining, $minimumWidths);
|
||||
|
||||
return $undersized->combine($distributeRemaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the longest word in the string.
|
||||
* @param string $str
|
||||
* @return int
|
||||
*/
|
||||
protected static function longestWordLength($str)
|
||||
{
|
||||
$words = preg_split('#[ /-]#', $str);
|
||||
$lengths = array_map(function ($s) {
|
||||
return strlen($s);
|
||||
}, $words);
|
||||
return max($lengths);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Transformations\Wrap;
|
||||
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
/**
|
||||
* Calculate the width of data in table cells in preparation for word wrapping.
|
||||
*/
|
||||
class ColumnWidths
|
||||
{
|
||||
protected $widths;
|
||||
|
||||
public function __construct($widths = [])
|
||||
{
|
||||
$this->widths = $widths;
|
||||
}
|
||||
|
||||
public function paddingSpace(
|
||||
$paddingInEachCell,
|
||||
$extraPaddingAtEndOfLine = 0,
|
||||
$extraPaddingAtBeginningOfLine = 0
|
||||
) {
|
||||
return ($extraPaddingAtBeginningOfLine + $extraPaddingAtEndOfLine + (count($this->widths) * $paddingInEachCell));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the columns that are shorter than the specified threshold.
|
||||
*/
|
||||
public function findShortColumns($thresholdWidth)
|
||||
{
|
||||
$thresholdWidths = array_fill_keys(array_keys($this->widths), $thresholdWidth);
|
||||
|
||||
return $this->findColumnsUnderThreshold($thresholdWidths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the columns that are shorter than the corresponding minimum widths.
|
||||
*/
|
||||
public function findUndersizedColumns($minimumWidths)
|
||||
{
|
||||
return $this->findColumnsUnderThreshold($minimumWidths->widths());
|
||||
}
|
||||
|
||||
protected function findColumnsUnderThreshold(array $thresholdWidths)
|
||||
{
|
||||
$shortColWidths = [];
|
||||
foreach ($this->widths as $key => $maxLength) {
|
||||
if (isset($thresholdWidths[$key]) && ($maxLength <= $thresholdWidths[$key])) {
|
||||
$shortColWidths[$key] = $maxLength;
|
||||
}
|
||||
}
|
||||
|
||||
return new ColumnWidths($shortColWidths);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the widths specified by this object do not fit within the
|
||||
* provided avaiable width, then reduce them all proportionally.
|
||||
*/
|
||||
public function adjustMinimumWidths($availableWidth, $dataCellWidths)
|
||||
{
|
||||
$result = $this->selectColumns($dataCellWidths->keys());
|
||||
if ($result->isEmpty()) {
|
||||
return $result;
|
||||
}
|
||||
$numberOfColumns = $dataCellWidths->count();
|
||||
|
||||
// How many unspecified columns are there?
|
||||
$unspecifiedColumns = $numberOfColumns - $result->count();
|
||||
$averageWidth = $this->averageWidth($availableWidth);
|
||||
|
||||
// Reserve some space for the columns that have no minimum.
|
||||
// Make sure they collectively get at least half of the average
|
||||
// width for each column. Or should it be a quarter?
|
||||
$reservedSpacePerColumn = ($averageWidth / 2);
|
||||
$reservedSpace = $reservedSpacePerColumn * $unspecifiedColumns;
|
||||
|
||||
// Calculate how much of the available space is remaining for use by
|
||||
// the minimum column widths after the reserved space is accounted for.
|
||||
$remainingAvailable = $availableWidth - $reservedSpace;
|
||||
|
||||
// Don't do anything if our widths fit inside the available widths.
|
||||
if ($result->totalWidth() <= $remainingAvailable) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Shrink the minimum widths if the table is too compressed.
|
||||
return $result->distribute($remainingAvailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return proportional weights
|
||||
*/
|
||||
public function distribute($availableWidth)
|
||||
{
|
||||
$result = [];
|
||||
$totalWidth = $this->totalWidth();
|
||||
$lastColumn = $this->lastColumn();
|
||||
$widths = $this->widths();
|
||||
|
||||
// Take off the last column, and calculate proportional weights
|
||||
// for the first N-1 columns.
|
||||
array_pop($widths);
|
||||
foreach ($widths as $key => $width) {
|
||||
$result[$key] = round(($width / $totalWidth) * $availableWidth);
|
||||
}
|
||||
|
||||
// Give the last column the rest of the available width
|
||||
$usedWidth = $this->sumWidth($result);
|
||||
$result[$lastColumn] = $availableWidth - $usedWidth;
|
||||
|
||||
return new ColumnWidths($result);
|
||||
}
|
||||
|
||||
public function lastColumn()
|
||||
{
|
||||
$keys = $this->keys();
|
||||
return array_pop($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of columns.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate how much space is available on average for all columns.
|
||||
*/
|
||||
public function averageWidth($availableWidth)
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
}
|
||||
return $availableWidth / $this->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the available keys (column identifiers) from the calculated
|
||||
* data set.
|
||||
*/
|
||||
public function keys()
|
||||
{
|
||||
return array_keys($this->widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the specified column.
|
||||
*/
|
||||
public function setWidth($key, $width)
|
||||
{
|
||||
$this->widths[$key] = $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the specified column.
|
||||
*/
|
||||
public function width($key)
|
||||
{
|
||||
return isset($this->widths[$key]) ? $this->widths[$key] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the lengths
|
||||
*/
|
||||
public function widths()
|
||||
{
|
||||
return $this->widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is no data in this object
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sum of the lengths of the provided widths.
|
||||
*/
|
||||
public function totalWidth()
|
||||
{
|
||||
return static::sumWidth($this->widths());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sum of the lengths of the provided widths.
|
||||
*/
|
||||
public static function sumWidth($widths)
|
||||
{
|
||||
return array_reduce(
|
||||
$widths,
|
||||
function ($carry, $item) {
|
||||
return $carry + $item;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that every item in $widths that has a corresponding entry
|
||||
* in $minimumWidths is as least as large as the minimum value held there.
|
||||
*/
|
||||
public function enforceMinimums($minimumWidths)
|
||||
{
|
||||
$result = [];
|
||||
if ($minimumWidths instanceof ColumnWidths) {
|
||||
$minimumWidths = $minimumWidths->widths();
|
||||
}
|
||||
$minimumWidths += $this->widths;
|
||||
|
||||
foreach ($this->widths as $key => $value) {
|
||||
$result[$key] = max($value, $minimumWidths[$key]);
|
||||
}
|
||||
|
||||
return new ColumnWidths($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all of the specified columns from this data structure.
|
||||
*/
|
||||
public function removeColumns($columnKeys)
|
||||
{
|
||||
$widths = $this->widths();
|
||||
|
||||
foreach ($columnKeys as $key) {
|
||||
unset($widths[$key]);
|
||||
}
|
||||
|
||||
return new ColumnWidths($widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all columns that exist in the provided list of keys.
|
||||
*/
|
||||
public function selectColumns($columnKeys)
|
||||
{
|
||||
$widths = [];
|
||||
|
||||
foreach ($columnKeys as $key) {
|
||||
if (isset($this->widths[$key])) {
|
||||
$widths[$key] = $this->width($key);
|
||||
}
|
||||
}
|
||||
|
||||
return new ColumnWidths($widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine this set of widths with another set, and return
|
||||
* a new set that contains the entries from both.
|
||||
*/
|
||||
public function combine(ColumnWidths $combineWith)
|
||||
{
|
||||
// Danger: array_merge renumbers numeric keys; that must not happen here.
|
||||
$combined = $combineWith->widths();
|
||||
foreach ($this->widths() as $key => $value) {
|
||||
$combined[$key] = $value;
|
||||
}
|
||||
return new ColumnWidths($combined);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Validate;
|
||||
|
||||
/**
|
||||
* Formatters may implement ValidDataTypesInterface in order to indicate
|
||||
* exactly which formats they support. The validDataTypes method can be
|
||||
* called to retrieve a list of data types useful in providing hints in
|
||||
* exception messages about which data types can be used with the formatter.
|
||||
*
|
||||
* Note that it is OPTIONAL for formatters to implement this interface.
|
||||
* If a formatter implements only ValidationInterface, then clients that
|
||||
* request the formatter via FormatterManager::write() will still get a list
|
||||
* (via an InvalidFormatException) of all of the formats that are usable
|
||||
* with the provided data type. Implementing ValidDataTypesInterface is
|
||||
* benefitial to clients who instantiate a formatter directly (via `new`).
|
||||
*
|
||||
* Formatters that implement ValidDataTypesInterface may wish to use
|
||||
* ValidDataTypesTrait.
|
||||
*/
|
||||
interface ValidDataTypesInterface extends ValidationInterface
|
||||
{
|
||||
/**
|
||||
* Return the list of data types acceptable to this formatter
|
||||
*
|
||||
* @return \ReflectionClass[]
|
||||
*/
|
||||
public function validDataTypes();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Validate;
|
||||
|
||||
/**
|
||||
* Provides a default implementation of isValidDataType.
|
||||
*
|
||||
* Users of this trait are expected to implement ValidDataTypesInterface.
|
||||
*/
|
||||
trait ValidDataTypesTrait
|
||||
{
|
||||
/**
|
||||
* Return the list of data types acceptable to this formatter
|
||||
*
|
||||
* @return \ReflectionClass[]
|
||||
*/
|
||||
abstract public function validDataTypes();
|
||||
|
||||
/**
|
||||
* Return the list of data types acceptable to this formatter
|
||||
*/
|
||||
public function isValidDataType(\ReflectionClass $dataType)
|
||||
{
|
||||
return array_reduce(
|
||||
$this->validDataTypes(),
|
||||
function ($carry, $supportedType) use ($dataType) {
|
||||
return
|
||||
$carry ||
|
||||
($dataType->getName() == $supportedType->getName()) ||
|
||||
($dataType->isSubclassOf($supportedType->getName()));
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Consolidation\OutputFormatters\Validate;
|
||||
|
||||
/**
|
||||
* Formatters may implement ValidationInterface in order to indicate
|
||||
* whether a particular data structure is supported. Any formatter that does
|
||||
* not implement ValidationInterface is assumed to only operate on arrays,
|
||||
* or data types that implement SimplifyToArrayInterface.
|
||||
*/
|
||||
interface ValidationInterface
|
||||
{
|
||||
/**
|
||||
* Return true if the specified format is valid for use with
|
||||
* this formatter.
|
||||
*/
|
||||
public function isValidDataType(\ReflectionClass $dataType);
|
||||
|
||||
/**
|
||||
* Throw an IncompatibleDataException if the provided data cannot
|
||||
* be processed by this formatter. Return the source data if it
|
||||
* is valid. The data may be encapsulated or converted if necessary.
|
||||
*
|
||||
* @param mixed $structuredData Data to validate
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function validate($structuredData);
|
||||
}
|
||||
Reference in New Issue
Block a user