Completed
Pull Request — master (#21)
by Greg
02:34
created

FormatterManager::validateData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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