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