02de79fff7ae40d7af8c64fe11b05166432b6bca
[yaffs-website] / vendor / consolidation / output-formatters / src / FormatterManager.php
1 <?php
2 namespace Consolidation\OutputFormatters;
3
4 use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
5 use Consolidation\OutputFormatters\Exception\InvalidFormatException;
6 use Consolidation\OutputFormatters\Exception\UnknownFormatException;
7 use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
8 use Consolidation\OutputFormatters\Formatters\FormatterInterface;
9 use Consolidation\OutputFormatters\Formatters\MetadataFormatterInterface;
10 use Consolidation\OutputFormatters\Formatters\RenderDataInterface;
11 use Consolidation\OutputFormatters\Options\FormatterOptions;
12 use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
13 use Consolidation\OutputFormatters\StructuredData\MetadataInterface;
14 use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
15 use Consolidation\OutputFormatters\Transformations\DomToArraySimplifier;
16 use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
17 use Consolidation\OutputFormatters\Transformations\SimplifyToArrayInterface;
18 use Consolidation\OutputFormatters\Validate\ValidationInterface;
19 use Symfony\Component\Console\Input\InputOption;
20 use Symfony\Component\Console\Output\OutputInterface;
21 use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
22
23 /**
24  * Manage a collection of formatters; return one on request.
25  */
26 class FormatterManager
27 {
28     /** var FormatterInterface[] */
29     protected $formatters = [];
30     /** var SimplifyToArrayInterface[] */
31     protected $arraySimplifiers = [];
32
33     public function __construct()
34     {
35     }
36
37     public function addDefaultFormatters()
38     {
39         $defaultFormatters = [
40             'string' => '\Consolidation\OutputFormatters\Formatters\StringFormatter',
41             'yaml' => '\Consolidation\OutputFormatters\Formatters\YamlFormatter',
42             'xml' => '\Consolidation\OutputFormatters\Formatters\XmlFormatter',
43             'json' => '\Consolidation\OutputFormatters\Formatters\JsonFormatter',
44             'print-r' => '\Consolidation\OutputFormatters\Formatters\PrintRFormatter',
45             'php' => '\Consolidation\OutputFormatters\Formatters\SerializeFormatter',
46             'var_export' => '\Consolidation\OutputFormatters\Formatters\VarExportFormatter',
47             'list' => '\Consolidation\OutputFormatters\Formatters\ListFormatter',
48             'csv' => '\Consolidation\OutputFormatters\Formatters\CsvFormatter',
49             'tsv' => '\Consolidation\OutputFormatters\Formatters\TsvFormatter',
50             'table' => '\Consolidation\OutputFormatters\Formatters\TableFormatter',
51             'sections' => '\Consolidation\OutputFormatters\Formatters\SectionsFormatter',
52         ];
53         if (class_exists('Symfony\Component\VarDumper\Dumper\CliDumper')) {
54              $defaultFormatters['var_dump'] = '\Consolidation\OutputFormatters\Formatters\VarDumpFormatter';
55         }
56         foreach ($defaultFormatters as $id => $formatterClassname) {
57             $formatter = new $formatterClassname;
58             $this->addFormatter($id, $formatter);
59         }
60         $this->addFormatter('', $this->formatters['string']);
61     }
62
63     public function addDefaultSimplifiers()
64     {
65         // Add our default array simplifier (DOMDocument to array)
66         $this->addSimplifier(new DomToArraySimplifier());
67     }
68
69     /**
70      * Add a formatter
71      *
72      * @param string $key the identifier of the formatter to add
73      * @param string $formatter the class name of the formatter to add
74      * @return FormatterManager
75      */
76     public function addFormatter($key, FormatterInterface $formatter)
77     {
78         $this->formatters[$key] = $formatter;
79         return $this;
80     }
81
82     /**
83      * Add a simplifier
84      *
85      * @param SimplifyToArrayInterface $simplifier the array simplifier to add
86      * @return FormatterManager
87      */
88     public function addSimplifier(SimplifyToArrayInterface $simplifier)
89     {
90         $this->arraySimplifiers[] = $simplifier;
91         return $this;
92     }
93
94     /**
95      * Return a set of InputOption based on the annotations of a command.
96      * @param FormatterOptions $options
97      * @return InputOption[]
98      */
99     public function automaticOptions(FormatterOptions $options, $dataType)
100     {
101         $automaticOptions = [];
102
103         // At the moment, we only support automatic options for --format
104         // and --fields, so exit if the command returns no data.
105         if (!isset($dataType)) {
106             return [];
107         }
108
109         $validFormats = $this->validFormats($dataType);
110         if (empty($validFormats)) {
111             return [];
112         }
113
114         $availableFields = $options->get(FormatterOptions::FIELD_LABELS);
115         $hasDefaultStringField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD);
116         $defaultFormat = $hasDefaultStringField ? 'string' : ($availableFields ? 'table' : 'yaml');
117
118         if (count($validFormats) > 1) {
119             // Make an input option for --format
120             $description = 'Format the result data. Available formats: ' . implode(',', $validFormats);
121             $automaticOptions[FormatterOptions::FORMAT] = new InputOption(FormatterOptions::FORMAT, '', InputOption::VALUE_REQUIRED, $description, $defaultFormat);
122         }
123
124         if ($availableFields) {
125             $defaultFields = $options->get(FormatterOptions::DEFAULT_FIELDS, [], '');
126             $description = 'Available fields: ' . implode(', ', $this->availableFieldsList($availableFields));
127             $automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, $description, $defaultFields);
128             $automaticOptions[FormatterOptions::FIELD] = new InputOption(FormatterOptions::FIELD, '', InputOption::VALUE_REQUIRED, "Select just one field, and force format to 'string'.", '');
129         }
130
131         return $automaticOptions;
132     }
133
134     /**
135      * Given a list of available fields, return a list of field descriptions.
136      * @return string[]
137      */
138     protected function availableFieldsList($availableFields)
139     {
140         return array_map(
141             function ($key) use ($availableFields) {
142                 return $availableFields[$key] . " ($key)";
143             },
144             array_keys($availableFields)
145         );
146     }
147
148     /**
149      * Return the identifiers for all valid data types that have been registered.
150      *
151      * @param mixed $dataType \ReflectionObject or other description of the produced data type
152      * @return array
153      */
154     public function validFormats($dataType)
155     {
156         $validFormats = [];
157         foreach ($this->formatters as $formatId => $formatterName) {
158             $formatter = $this->getFormatter($formatId);
159             if (!empty($formatId) && $this->isValidFormat($formatter, $dataType)) {
160                 $validFormats[] = $formatId;
161             }
162         }
163         sort($validFormats);
164         return $validFormats;
165     }
166
167     public function isValidFormat(FormatterInterface $formatter, $dataType)
168     {
169         if (is_array($dataType)) {
170             $dataType = new \ReflectionClass('\ArrayObject');
171         }
172         if (!is_object($dataType) && !class_exists($dataType)) {
173             return false;
174         }
175         if (!$dataType instanceof \ReflectionClass) {
176             $dataType = new \ReflectionClass($dataType);
177         }
178         return $this->isValidDataType($formatter, $dataType);
179     }
180
181     public function isValidDataType(FormatterInterface $formatter, \ReflectionClass $dataType)
182     {
183         if ($this->canSimplifyToArray($dataType)) {
184             if ($this->isValidFormat($formatter, [])) {
185                 return true;
186             }
187         }
188         // If the formatter does not implement ValidationInterface, then
189         // it is presumed that the formatter only accepts arrays.
190         if (!$formatter instanceof ValidationInterface) {
191             return $dataType->isSubclassOf('ArrayObject') || ($dataType->getName() == 'ArrayObject');
192         }
193         return $formatter->isValidDataType($dataType);
194     }
195
196     /**
197      * Format and write output
198      *
199      * @param OutputInterface $output Output stream to write to
200      * @param string $format Data format to output in
201      * @param mixed $structuredOutput Data to output
202      * @param FormatterOptions $options Formatting options
203      */
204     public function write(OutputInterface $output, $format, $structuredOutput, FormatterOptions $options)
205     {
206         $formatter = $this->getFormatter((string)$format);
207         if (!is_string($structuredOutput) && !$this->isValidFormat($formatter, $structuredOutput)) {
208             $validFormats = $this->validFormats($structuredOutput);
209             throw new InvalidFormatException((string)$format, $structuredOutput, $validFormats);
210         }
211         if ($structuredOutput instanceof FormatterAwareInterface) {
212             $structuredOutput->setFormatter($formatter);
213         }
214         // Give the formatter a chance to override the options
215         $options = $this->overrideOptions($formatter, $structuredOutput, $options);
216         $restructuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options);
217         if ($formatter instanceof MetadataFormatterInterface) {
218             $formatter->writeMetadata($output, $structuredOutput, $options);
219         }
220         $formatter->write($output, $restructuredOutput, $options);
221     }
222
223     protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
224     {
225         // Give the formatter a chance to do something with the
226         // raw data before it is restructured.
227         $overrideRestructure = $this->overrideRestructure($formatter, $structuredOutput, $options);
228         if ($overrideRestructure) {
229             return $overrideRestructure;
230         }
231
232         // Restructure the output data (e.g. select fields to display, etc.).
233         $restructuredOutput = $this->restructureData($structuredOutput, $options);
234
235         // Make sure that the provided data is in the correct format for the selected formatter.
236         $restructuredOutput = $this->validateData($formatter, $restructuredOutput, $options);
237
238         // Give the original data a chance to re-render the structured
239         // output after it has been restructured and validated.
240         $restructuredOutput = $this->renderData($formatter, $structuredOutput, $restructuredOutput, $options);
241
242         return $restructuredOutput;
243     }
244
245     /**
246      * Fetch the requested formatter.
247      *
248      * @param string $format Identifier for requested formatter
249      * @return FormatterInterface
250      */
251     public function getFormatter($format)
252     {
253         // The client must inject at least one formatter before asking for
254         // any formatters; if not, we will provide all of the usual defaults
255         // as a convenience.
256         if (empty($this->formatters)) {
257             $this->addDefaultFormatters();
258             $this->addDefaultSimplifiers();
259         }
260         if (!$this->hasFormatter($format)) {
261             throw new UnknownFormatException($format);
262         }
263         $formatter = $this->formatters[$format];
264         return $formatter;
265     }
266
267     /**
268      * Test to see if the stipulated format exists
269      */
270     public function hasFormatter($format)
271     {
272         return array_key_exists($format, $this->formatters);
273     }
274
275     /**
276      * Render the data as necessary (e.g. to select or reorder fields).
277      *
278      * @param FormatterInterface $formatter
279      * @param mixed $originalData
280      * @param mixed $restructuredData
281      * @param FormatterOptions $options Formatting options
282      * @return mixed
283      */
284     public function renderData(FormatterInterface $formatter, $originalData, $restructuredData, FormatterOptions $options)
285     {
286         if ($formatter instanceof RenderDataInterface) {
287             return $formatter->renderData($originalData, $restructuredData, $options);
288         }
289         return $restructuredData;
290     }
291
292     /**
293      * Determine if the provided data is compatible with the formatter being used.
294      *
295      * @param FormatterInterface $formatter Formatter being used
296      * @param mixed $structuredOutput Data to validate
297      * @return mixed
298      */
299     public function validateData(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
300     {
301         // If the formatter implements ValidationInterface, then let it
302         // test the data and throw or return an error
303         if ($formatter instanceof ValidationInterface) {
304             return $formatter->validate($structuredOutput);
305         }
306         // If the formatter does not implement ValidationInterface, then
307         // it will never be passed an ArrayObject; we will always give
308         // it a simple array.
309         $structuredOutput = $this->simplifyToArray($structuredOutput, $options);
310         // If we could not simplify to an array, then throw an exception.
311         // We will never give a formatter anything other than an array
312         // unless it validates that it can accept the data type.
313         if (!is_array($structuredOutput)) {
314             throw new IncompatibleDataException(
315                 $formatter,
316                 $structuredOutput,
317                 []
318             );
319         }
320         return $structuredOutput;
321     }
322
323     protected function simplifyToArray($structuredOutput, FormatterOptions $options)
324     {
325         // We can do nothing unless the provided data is an object.
326         if (!is_object($structuredOutput)) {
327             return $structuredOutput;
328         }
329         // Check to see if any of the simplifiers can convert the given data
330         // set to an array.
331         $outputDataType = new \ReflectionClass($structuredOutput);
332         foreach ($this->arraySimplifiers as $simplifier) {
333             if ($simplifier->canSimplify($outputDataType)) {
334                 $structuredOutput = $simplifier->simplifyToArray($structuredOutput, $options);
335             }
336         }
337         // Convert data structure back into its original form, if necessary.
338         if ($structuredOutput instanceof OriginalDataInterface) {
339             return $structuredOutput->getOriginalData();
340         }
341         // Convert \ArrayObjects to a simple array.
342         if ($structuredOutput instanceof \ArrayObject) {
343             return $structuredOutput->getArrayCopy();
344         }
345         return $structuredOutput;
346     }
347
348     protected function canSimplifyToArray(\ReflectionClass $structuredOutput)
349     {
350         foreach ($this->arraySimplifiers as $simplifier) {
351             if ($simplifier->canSimplify($structuredOutput)) {
352                 return true;
353             }
354         }
355         return false;
356     }
357
358     /**
359      * Restructure the data as necessary (e.g. to select or reorder fields).
360      *
361      * @param mixed $structuredOutput
362      * @param FormatterOptions $options
363      * @return mixed
364      */
365     public function restructureData($structuredOutput, FormatterOptions $options)
366     {
367         if ($structuredOutput instanceof RestructureInterface) {
368             return $structuredOutput->restructure($options);
369         }
370         return $structuredOutput;
371     }
372
373     /**
374      * Allow the formatter access to the raw structured data prior
375      * to restructuring.  For example, the 'list' formatter may wish
376      * to display the row keys when provided table output.  If this
377      * function returns a result that does not evaluate to 'false',
378      * then that result will be used as-is, and restructuring and
379      * validation will not occur.
380      *
381      * @param mixed $structuredOutput
382      * @param FormatterOptions $options
383      * @return mixed
384      */
385     public function overrideRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
386     {
387         if ($formatter instanceof OverrideRestructureInterface) {
388             return $formatter->overrideRestructure($structuredOutput, $options);
389         }
390     }
391
392     /**
393      * Allow the formatter to mess with the configuration options before any
394      * transformations et. al. get underway.
395      * @param FormatterInterface $formatter
396      * @param mixed $structuredOutput
397      * @param FormatterOptions $options
398      * @return FormatterOptions
399      */
400     public function overrideOptions(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
401     {
402         if ($formatter instanceof OverrideOptionsInterface) {
403             return $formatter->overrideOptions($structuredOutput, $options);
404         }
405         return $options;
406     }
407 }