Completed
Pull Request — master (#23)
by Greg
03:09
created

FormatterManager::overrideOptions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 3
crap 6
1
<?php
2
namespace Consolidation\OutputFormatters;
3
4
use Symfony\Component\Console\Output\OutputInterface;
5
use Consolidation\OutputFormatters\Exception\UnknownFormatException;
6
use Consolidation\OutputFormatters\Formatters\RenderDataInterface;
7
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
8
use Consolidation\OutputFormatters\Transformations\DomToArraySimplifier;
9
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
10
11
/**
12
 * Manage a collection of formatters; return one on request.
13
 */
14
class FormatterManager
15
{
16 28
    protected $formatters = [];
17
    protected $arraySimplifiers = [];
18 28
19 28
    public function __construct()
20 28
    {
21 28
        $this->formatters = [
22 28
            'string' => '\Consolidation\OutputFormatters\Formatters\StringFormatter',
23 28
            'yaml' => '\Consolidation\OutputFormatters\Formatters\YamlFormatter',
24 28
            'xml' => '\Consolidation\OutputFormatters\Formatters\XmlFormatter',
25 28
            'json' => '\Consolidation\OutputFormatters\Formatters\JsonFormatter',
26 28
            'print-r' => '\Consolidation\OutputFormatters\Formatters\PrintRFormatter',
27 28
            'php' => '\Consolidation\OutputFormatters\Formatters\SerializeFormatter',
28
            'var_export' => '\Consolidation\OutputFormatters\Formatters\VarExportFormatter',
29
            'list' => '\Consolidation\OutputFormatters\Formatters\ListFormatter',
30
            'csv' => '\Consolidation\OutputFormatters\Formatters\CsvFormatter',
31 28
            'tsv' => '\Consolidation\OutputFormatters\Formatters\TsvFormatter',
32 28
            'table' => '\Consolidation\OutputFormatters\Formatters\TableFormatter',
33
            'sections' => '\Consolidation\OutputFormatters\Formatters\SectionsFormatter',
34
        ];
35
36
        // Make the empty format an alias for the 'string' formatter.
37
        $this->addFormatter('', $this->formatters['string']);
38
39
        // Add our default array simplifier (DOMDocument to array)
40
        $this->addSimplifier(new DomToArraySimplifier());
41
    }
42
43 28
    /**
44
     * Add a formatter
45 28
     *
46 27
     * @param string $key the identifier of the formatter to add
47 24
     * @param string $formatterClassname the class name of the formatter to add
48 24
     * @return FormatterManager
49
     */
50 27
    public function addFormatter($key, $formatterClassname)
51
    {
52
        $this->formatters[$key] = $formatterClassname;
53
        return $this;
54 27
    }
55 27
56 4
    /**
57
     * Add a simplifier
58
     *
59
     * @param SimplifyToArrayInterface $simplifier the array simplifier to add
60 27
     * @return FormatterManager
61
     */
62
    public function addSimplifier(SimplifyToArrayInterface $simplifier)
63 27
    {
64
        $this->arraySimplifiers[] = $simplifier;
65
        return $this;
66
    }
67 24
68
    /**
69 24
     * Return the identifiers for all valid data types that have been registered.
70
     *
71
     * @param mixed $dataType \ReflectionObject or other description of the produced data type
72
     * @return array
73
     */
74
    public function validFormats($dataType)
75
    {
76
        $validFormats = [];
77
        foreach ($this->formatters as $formatId => $formatterName) {
78
            $formatter = $this->getFormatter($formatId);
79 28
            if ($this->isValidFormat($formatter, $dataType)) {
80
                $validFormats[] = $formatId;
81 28
            }
82 1
        }
83
        sort($validFormats);
84
        return $validFormats;
85 27
    }
86 27
87 9
    public function isValidFormat(FormatterInterface $formatter, $dataType)
88 9
    {
89 27
        if (is_array($dataType)) {
90
            $dataType = new \ReflectionClass('\ArrayObject');
91
        }
92 28
        if (!$dataType instanceof \ReflectionClass) {
93
            $dataType = new \ReflectionClass($dataType);
94 28
        }
95
        // If the formatter does not implement ValidationInterface, then
96
        // it is presumed that the formatter only accepts arrays.
97
        if (!$formatter instanceof ValidationInterface) {
98
            return $dataType->isSubclassOf('ArrayObject') || ($dataType->getName() == 'ArrayObject');
99
        }
100
        $supportedTypes = $formatter->validDataTypes();
101
        foreach ($supportedTypes as $supportedType) {
102
            if (($dataType->getName() == $supportedType->getName()) || $dataType->isSubclassOf($supportedType->getName())) {
103
                return true;
104
            }
105
        }
106
        return false;
107 24
    }
108
109 24
    /**
110 14
     * Format and write output
111
     *
112 14
     * @param OutputInterface $output Output stream to write to
113
     * @param string $format Data format to output in
114
     * @param mixed $structuredOutput Data to output
115
     * @param FormatterOptions $options Formatting options
116
     */
117
    public function write(OutputInterface $output, $format, $structuredOutput, FormatterOptions $options)
118
    {
119
        $formatter = $this->getFormatter((string)$format);
120
        // Give the formatter a chance to override the options
121
        $options = $this->overrideOptions($formatter, $structuredOutput, $options);
122 27
        $structuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options);
123
        $formatter->write($output, $structuredOutput, $options);
124
    }
125
126 27
    protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
127 16
    {
128
        // Give the formatter a chance to do something with the
129
        // raw data before it is restructured.
130
        $overrideRestructure = $this->overrideRestructure($formatter, $structuredOutput, $options);
131
        if ($overrideRestructure) {
132 15
            return $overrideRestructure;
133 4
        }
134
135
        // Restructure the output data (e.g. select fields to display, etc.).
136 11
        $restructuredOutput = $this->restructureData($structuredOutput, $options);
137
138
        // Make sure that the provided data is in the correct format for the selected formatter.
139
        $restructuredOutput = $this->validateData($formatter, $restructuredOutput, $options);
140
141
        // Give the original data a chance to re-render the structured
142
        // output after it has been restructured and validated.
143
        $restructuredOutput = $this->renderData($formatter, $structuredOutput, $restructuredOutput, $options);
144
145
        return $restructuredOutput;
146
    }
147 27
148
    /**
149 27
     * Fetch the requested formatter.
150 7
     *
151
     * @param string $format Identifier for requested formatter
152 20
     * @return FormatterInterface
153
     */
154
    public function getFormatter($format)
155
    {
156
        if (!$this->hasFormatter($format)) {
157
            throw new UnknownFormatException($format);
158
        }
159
        $formatter = new $this->formatters[$format];
160
        return $formatter;
161
    }
162
163
    /**
164
     * Test to see if the stipulated format exists
165
     */
166
    public function hasFormatter($format)
167
    {
168 27
        return array_key_exists($format, $this->formatters);
169
    }
170 27
171 5
    /**
172
     * Render the data as necessary (e.g. to select or reorder fields).
173 26
     *
174
     * @param FormatterInterface $formatter
175
     * @param mixed $originalData
176
     * @param mixed $restructuredData
177
     * @param FormatterOptions $options Formatting options
178
     * @return mixed
179
     */
180
    public function renderData(FormatterInterface $formatter, $originalData, $restructuredData, FormatterOptions $options)
181
    {
182
        if ($formatter instanceof RenderDataInterface) {
183
            return $formatter->renderData($originalData, $restructuredData, $options);
184
        }
185
        return $restructuredData;
186
    }
187
188
    /**
189
     * Determine if the provided data is compatible with the formatter being used.
190
     *
191
     * @param FormatterInterface $formatter Formatter being used
192
     * @param mixed $structuredOutput Data to validate
193
     * @return mixed
194
     */
195
    public function validateData(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
196
    {
197
        // If the formatter implements ValidationInterface, then let it
198
        // test the data and throw or return an error
199
        if ($formatter instanceof ValidationInterface) {
200
            return $formatter->validate($structuredOutput);
201
        }
202
        // If the formatter does not implement ValidationInterface, then
203
        // it will never be passed an ArrayObject; we will always give
204
        // it a simple array.
205
        $structuredOutput = $this->simplifyToArray($structuredOutput, $options);
206
        // If we could not simplify to an array, then throw an exception.
207
        // We will never give a formatter anything other than an array
208
        // unless it validates that it can accept the data type.
209
        if (!is_array($structuredOutput)) {
210
            throw new IncompatibleDataException(
211
                $formatter,
212
                $structuredOutput,
213
                []
214
            );
215
        }
216
        return $structuredOutput;
217
    }
218
219
    protected function simplifyToArray($structuredOutput, FormatterOptions $options)
220
    {
221
        // Check to see if any of the simplifiers can convert the given data
222
        // set to an array.
223
        foreach ($this->arraySimplifiers as $simplifier) {
224
            $structuredOutput = $simplifier->simplifyToArray($structuredOutput, $options);
225
        }
226
        // Convert \ArrayObjects to a simple array.
227
        if ($structuredOutput instanceof \ArrayObject) {
228
            return $structuredOutput->getArrayCopy();
229
        }
230
        return $structuredOutput;
231
    }
232
233
    /**
234
     * Restructure the data as necessary (e.g. to select or reorder fields).
235
     *
236
     * @param mixed $structuredOutput
237
     * @param FormatterOptions $options
238
     * @return mixed
239
     */
240
    public function restructureData($structuredOutput, FormatterOptions $options)
241
    {
242
        if ($structuredOutput instanceof RestructureInterface) {
243
            return $structuredOutput->restructure($options);
244
        }
245
        return $structuredOutput;
246
    }
247
248
    /**
249
     * Allow the formatter access to the raw structured data prior
250
     * to restructuring.  For example, the 'list' formatter may wish
251
     * to display the row keys when provided table output.  If this
252
     * function returns a result that does not evaluate to 'false',
253
     * then that result will be used as-is, and restructuring and
254
     * validation will not occur.
255
     *
256
     * @param mixed $structuredOutput
257
     * @param FormatterOptions $options
258
     * @return mixed
259
     */
260
    public function overrideRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
261
    {
262
        if ($formatter instanceof OverrideRestructureInterface) {
263
            return $formatter->overrideRestructure($structuredOutput, $options);
264
        }
265
    }
266
267
    /**
268
     * Allow the formatter to mess with the configuration options before any
269
     * transformations et. al. get underway.
270
     * @param FormatterInterface $formatter
271
     * @param mixed $structuredOutput
272
     * @param FormatterOptions $options
273
     * @return FormatterOptions
274
     */
275
    public function overrideOptions(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
276
    {
277
        if ($formatter instanceof OverrideOptionsInterface) {
278
            return $formatter->overrideOptions($structuredOutput, $options);
279
        }
280
        return $options;
281
    }
282
}
283