From f43aeb9a8621b8955879bc24823fe27469fe13e1 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Thu, 28 Apr 2016 16:43:10 -0700 Subject: [PATCH] Provide valid formats from the format manager given a data type to format. --- src/Exception/IncompatibleDataException.php | 23 ++++--- src/FormatterManager.php | 68 ++++++++++++++++++- src/Formatters/CsvFormatter.php | 2 +- src/Formatters/StringFormatter.php | 22 +++++- src/StructuredData/AbstractStructuredList.php | 55 +++++++++++++++ src/StructuredData/AssociativeList.php | 9 +-- src/StructuredData/RowsOfFields.php | 39 +---------- tests/testFormatters.php | 54 +++++++++++++++ 8 files changed, 214 insertions(+), 58 deletions(-) create mode 100644 src/StructuredData/AbstractStructuredList.php diff --git a/src/Exception/IncompatibleDataException.php b/src/Exception/IncompatibleDataException.php index 1a1e168..fdd8bfd 100644 --- a/src/Exception/IncompatibleDataException.php +++ b/src/Exception/IncompatibleDataException.php @@ -17,21 +17,28 @@ public function __construct(FormatterInterface $formatter, $data, $allowedTypes) parent::__construct($message, 1); } + /** + * 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 ($data instanceof \ReflectionClass) { + 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_object($data)) { - return 'an instance of ' . get_class($data); - } - if (is_array($data)) { - return 'an array'; - } if (is_string($data)) { return 'a string'; } - return '<' . var_export($data) . '>'; + 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) diff --git a/src/FormatterManager.php b/src/FormatterManager.php index 60b8b35..4893f21 100644 --- a/src/FormatterManager.php +++ b/src/FormatterManager.php @@ -2,7 +2,6 @@ namespace Consolidation\OutputFormatters; use Symfony\Component\Console\Output\OutputInterface; -use Consolidation\OutputFormatters\FormatterInterface; use Consolidation\OutputFormatters\Exception\UnknownFormatException; use Consolidation\OutputFormatters\Formatters\RenderDataInterface; @@ -32,6 +31,58 @@ public function __construct() $this->formatters[''] = $this->formatters['string']; } + /** + * Add a formatter + * + * @param FormatterInterface $formatter the formatter to add + * @return FormatterManager + */ + public function add(FormatterInterface $formatter) + { + $this->formatters[] = $formatter; + return $this; + } + + /** + * 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 ($this->isValidFormat($formatter, $dataType)) { + $validFormats[] = $formatId; + } + } + return $validFormats; + } + + public function isValidFormat(FormatterInterface $formatter, $dataType) + { + if (is_array($dataType)) { + $dataType = new \ReflectionClass('\ArrayObject'); + } + if (!$dataType instanceOf \ReflectionClass) { + $dataType = new \ReflectionClass($dataType); + } + // 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'); + } + $supportedTypes = $formatter->validDataTypes(); + foreach ($supportedTypes as $supportedType) { + if (($dataType->getName() == $supportedType->getName()) || $dataType->isSubclassOf($supportedType->getName())) { + return true; + } + } + return false; + } + /** * Format and write output * @@ -47,6 +98,8 @@ public function write(OutputInterface $output, $format, $structuredOutput, Forma $formatter->write($output, $structuredOutput, $options); } + + protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options) { // Give the formatter a chance to do something with the @@ -84,6 +137,9 @@ public function getFormatter($format) return $formatter; } + /** + * Test to see if the stipulated format exists + */ public function hasFormatter($format) { return array_key_exists($format, $this->formatters); @@ -126,7 +182,15 @@ public function validateData(FormatterInterface $formatter, $structuredOutput) if ($structuredOutput instanceof \ArrayObject) { return $structuredOutput->getArrayCopy(); } - + // If the formatter does not implmeent ValidationInterface, then + // we will further presume that it will *only* accept arrays. + if (!is_array($structuredOutput)) { + throw new IncompatibleDataException( + $formatter, + $structuredOutput, + [] + ); + } return $structuredOutput; } diff --git a/src/Formatters/CsvFormatter.php b/src/Formatters/CsvFormatter.php index f0aff15..e6def85 100644 --- a/src/Formatters/CsvFormatter.php +++ b/src/Formatters/CsvFormatter.php @@ -29,7 +29,7 @@ public function validDataTypes() [ new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'), new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\AssociativeList'), - [], + new \ReflectionClass('\ArrayObject'), ]; } diff --git a/src/Formatters/StringFormatter.php b/src/Formatters/StringFormatter.php index edca9b7..dfa1d49 100644 --- a/src/Formatters/StringFormatter.php +++ b/src/Formatters/StringFormatter.php @@ -2,6 +2,7 @@ namespace Consolidation\OutputFormatters\Formatters; use Consolidation\OutputFormatters\FormatterInterface; +use Consolidation\OutputFormatters\ValidationInterface; use Consolidation\OutputFormatters\FormatterOptions; use Symfony\Component\Console\Output\OutputInterface; @@ -13,7 +14,7 @@ * provided data only if it is a string; if any other * type is given, then nothing is printed. */ -class StringFormatter implements FormatterInterface +class StringFormatter implements FormatterInterface, ValidationInterface { /** * @inheritdoc @@ -24,4 +25,23 @@ public function write(OutputInterface $output, $data, FormatterOptions $options) $output->writeln($data); } } + + /** + * Do not return any valid data types -- this formatter will never show up + * in a list of valid formats. + */ + public function validDataTypes() + { + return []; + } + + /** + * 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; + } } diff --git a/src/StructuredData/AbstractStructuredList.php b/src/StructuredData/AbstractStructuredList.php new file mode 100644 index 0000000..5b9ac70 --- /dev/null +++ b/src/StructuredData/AbstractStructuredList.php @@ -0,0 +1,55 @@ +defaultOptions(); + + $reorderer = new ReorderFields(); + $fieldLabels = $reorderer->reorder($options->get('fields', $defaults), $options->get('field-labels', $defaults), $data); + + $tableTransformer = new TableTransformation($data, $fieldLabels, $options->get('row-labels', $defaults)); + if ($options->get('list-orientation', $defaults)) { + $tableTransformer->setLayout(TableTransformation::LIST_LAYOUT); + } + + return $tableTransformer; + } + + protected function defaultOptions() + { + return [ + 'list-orientation' => false, + 'fields' => [], + 'field-labels' => [], + 'row-labels' => [], + 'default-fields' => [], + ]; + } +} diff --git a/src/StructuredData/AssociativeList.php b/src/StructuredData/AssociativeList.php index 4c5b5c5..03cb7a0 100644 --- a/src/StructuredData/AssociativeList.php +++ b/src/StructuredData/AssociativeList.php @@ -13,15 +13,8 @@ * key : value pair. The keys must be unique, as is typically * the case for associative arrays. */ -class AssociativeList extends RowsOfFields implements ListDataInterface +class AssociativeList extends AbstractStructuredList { - protected $data; - - public function __construct($data) - { - parent::__construct($data); - } - public function restructure(FormatterOptions $options) { $data = [$this->getArrayCopy()]; diff --git a/src/StructuredData/RowsOfFields.php b/src/StructuredData/RowsOfFields.php index b219c03..2875c18 100644 --- a/src/StructuredData/RowsOfFields.php +++ b/src/StructuredData/RowsOfFields.php @@ -1,11 +1,7 @@ getArrayCopy(); @@ -33,30 +22,4 @@ public function getListData() { return array_keys($this->getArrayCopy()); } - - protected function createTableTransformation($data, $options) - { - $defaults = $this->defaultOptions(); - - $reorderer = new ReorderFields(); - $fieldLabels = $reorderer->reorder($options->get('fields', $defaults), $options->get('field-labels', $defaults), $data); - - $tableTransformer = new TableTransformation($data, $fieldLabels, $options->get('row-labels', $defaults)); - if ($options->get('list-orientation', $defaults)) { - $tableTransformer->setLayout(TableTransformation::LIST_LAYOUT); - } - - return $tableTransformer; - } - - protected function defaultOptions() - { - return [ - 'list-orientation' => false, - 'fields' => [], - 'field-labels' => [], - 'row-labels' => [], - 'default-fields' => [], - ]; - } } diff --git a/tests/testFormatters.php b/tests/testFormatters.php index 282b3f9..96091b1 100644 --- a/tests/testFormatters.php +++ b/tests/testFormatters.php @@ -24,6 +24,60 @@ function setup() { //$this->logger = new Logger($this->output); } + function testValidFormats() + { + $arrayObjectRef = new \ReflectionClass('\ArrayObject'); + $associativeListRef = new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\AssociativeList'); + $rowsOfFieldsRef = new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'); + $notADataType = new \ReflectionClass('\Consolidation\OutputFormatters\FormatterManager'); + + $jsonFormatter = $this->formatterManager->getFormatter('json'); + $isValid = $this->formatterManager->isValidFormat($jsonFormatter, $notADataType); + $this->assertFalse($isValid); + $isValid = $this->formatterManager->isValidFormat($jsonFormatter, new \ArrayObject()); + $this->assertTrue($isValid); + $isValid = $this->formatterManager->isValidFormat($jsonFormatter, $arrayObjectRef); + $this->assertTrue($isValid); + $isValid = $this->formatterManager->isValidFormat($jsonFormatter, []); + $this->assertTrue($isValid); + $isValid = $this->formatterManager->isValidFormat($jsonFormatter, $associativeListRef); + $this->assertTrue($isValid); + $isValid = $this->formatterManager->isValidFormat($jsonFormatter, $rowsOfFieldsRef); + $this->assertTrue($isValid); + + $sectionsFormatter = $this->formatterManager->getFormatter('sections'); + $isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $notADataType); + $this->assertFalse($isValid); + $isValid = $this->formatterManager->isValidFormat($sectionsFormatter, []); + $this->assertFalse($isValid); + $isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $arrayObjectRef); + $this->assertFalse($isValid); + $isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $rowsOfFieldsRef); + $this->assertTrue($isValid); + $isValid = $this->formatterManager->isValidFormat($sectionsFormatter, $associativeListRef); + $this->assertFalse($isValid); + + // Check to see which formats can handle a simple array + $validFormats = $this->formatterManager->validFormats([]); + sort($validFormats); + $this->assertEquals('csv,json,list,php,print-r,var_export,yaml', implode(',', $validFormats)); + + // Check to see which formats can handle an AssociativeList + $validFormats = $this->formatterManager->validFormats($associativeListRef); + sort($validFormats); + $this->assertEquals('csv,json,list,php,print-r,table,var_export,yaml', implode(',', $validFormats)); + + // Check to see which formats can handle an RowsOfFields + $validFormats = $this->formatterManager->validFormats(new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields')); + sort($validFormats); + $this->assertEquals('csv,json,list,php,print-r,sections,table,var_export,yaml', implode(',', $validFormats)); + + // Test error case: no formatter should handle something that is not a data type. + $validFormats = $this->formatterManager->validFormats($notADataType); + sort($validFormats); + $this->assertEquals('', implode(',', $validFormats)); + } + function assertFormattedOutputMatches($expected, $format, $data, FormatterOptions $options = null, $userOptions = []) { if (!$options) { $options = new FormatterOptions();