Completed
Push — master ( e5a757...47ec18 )
by Robbie
14s
created

XMLDataFormatter   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 185
Duplicated Lines 26.49 %

Importance

Changes 0
Metric Value
wmc 41
dl 49
loc 185
rs 8.2769
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
D convertDataObjectWithoutHeader() 49 116 33
A supportedExtensions() 0 4 1
A supportedMimeTypes() 0 5 1
A convertStringToArray() 0 3 1
A convertDataObjectSet() 0 13 3
A convertDataObject() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like XMLDataFormatter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XMLDataFormatter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\RestfulServer\DataFormatter;
4
5
use SilverStripe\RestfulServer\DataFormatter;
6
use SilverStripe\ORM\DataObjectInterface;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\ORM\SS_List;
12
13
/**
14
 * Formats a DataObject's member fields into an XML string
15
 */
16
class XMLDataFormatter extends DataFormatter
17
{
18
19
    /**
20
     * @config
21
     * @todo pass this from the API to the data formatter somehow
22
     */
23
    private static $api_base = "api/v1/";
24
25
    protected $outputContentType = 'text/xml';
26
27
    public function supportedExtensions()
28
    {
29
        return array(
30
            'xml'
31
        );
32
    }
33
34
    public function supportedMimeTypes()
35
    {
36
        return array(
37
            'text/xml',
38
            'application/xml',
39
        );
40
    }
41
42
    /**
43
     * Generate an XML representation of the given {@link DataObject}.
44
     *
45
     * @param DataObject $obj
46
     * @param $includeHeader Include <?xml ...?> header (Default: true)
0 ignored issues
show
Bug introduced by
The type SilverStripe\RestfulServer\DataFormatter\Include was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
47
     * @return String XML
48
     */
49
    public function convertDataObject(DataObjectInterface $obj, $fields = null)
50
    {
51
        $response = Controller::curr()->getResponse();
52
        if ($response) {
53
            $response->addHeader("Content-Type", "text/xml");
54
        }
55
56
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields);
57
    }
58
59
    public function convertDataObjectWithoutHeader(DataObject $obj, $fields = null, $relations = null)
60
    {
61
        $className = $this->sanitiseClassName(get_class($obj));
62
        $id = $obj->ID;
63
        $objHref = Director::absoluteURL($this->config()->api_base . "$className/$obj->ID");
64
65
        $xml = "<$className href=\"$objHref.xml\">\n";
66
        foreach ($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
67
            // Field filtering
68
            if ($fields && !in_array($fieldName, $fields)) {
69
                continue;
70
            }
71
            $fieldValue = $obj->obj($fieldName)->forTemplate();
72
            if (!mb_check_encoding($fieldValue, 'utf-8')) {
73
                $fieldValue = "(data is badly encoded)";
74
            }
75
76
            if (is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toXML')) {
77
                $xml .= $fieldValue->toXML();
78
            } else {
79
                if ('HTMLText' == $fieldType) {
80
                    // Escape HTML values using CDATA
81
                    $fieldValue = sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $fieldValue));
82
                } else {
83
                    $fieldValue = Convert::raw2xml($fieldValue);
84
                }
85
                $xml .= "<$fieldName>$fieldValue</$fieldName>\n";
86
            }
87
        }
88
89
        if ($this->relationDepth > 0) {
90
            foreach ($obj->hasOne() as $relName => $relClass) {
91
                if (!singleton($relClass)->stat('api_access')) {
92
                    continue;
93
                }
94
95
                // Field filtering
96
                if ($fields && !in_array($relName, $fields)) {
97
                    continue;
98
                }
99
                if ($this->customRelations && !in_array($relName, $this->customRelations)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRelations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
100
                    continue;
101
                }
102
103
                $fieldName = $relName . 'ID';
104 View Code Duplication
                if ($obj->$fieldName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
                    $href = Director::absoluteURL($this->config()->api_base . "$relClass/" . $obj->$fieldName);
106
                } else {
107
                    $href = Director::absoluteURL($this->config()->api_base . "$className/$id/$relName");
108
                }
109
                $xml .= "<$relName linktype=\"has_one\" href=\"$href.xml\" id=\"" . $obj->$fieldName
110
                    . "\"></$relName>\n";
111
            }
112
113 View Code Duplication
            foreach ($obj->hasMany() as $relName => $relClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
                //remove dot notation from relation names
115
                $parts = explode('.', $relClass);
116
                $relClass = array_shift($parts);
117
                if (!singleton($relClass)->stat('api_access')) {
118
                    continue;
119
                }
120
                // backslashes in FQCNs kills both URIs and XML
121
                $relClass = $this->sanitiseClassName($relClass);
122
123
                // Field filtering
124
                if ($fields && !in_array($relName, $fields)) {
125
                    continue;
126
                }
127
                if ($this->customRelations && !in_array($relName, $this->customRelations)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRelations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
128
                    continue;
129
                }
130
131
                $xml .= "<$relName linktype=\"has_many\" href=\"$objHref/$relName.xml\">\n";
132
                $items = $obj->$relName();
133
                if ($items) {
134
                    foreach ($items as $item) {
135
                        $href = Director::absoluteURL($this->config()->api_base . "$relClass/$item->ID");
136
                        $xml .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\"></$relClass>\n";
137
                    }
138
                }
139
                $xml .= "</$relName>\n";
140
            }
141
142 View Code Duplication
            foreach ($obj->manyMany() as $relName => $relClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
                //remove dot notation from relation names
144
                $parts = explode('.', $relClass);
145
                $relClass = array_shift($parts);
146
                if (!singleton($relClass)->stat('api_access')) {
147
                    continue;
148
                }
149
                // backslashes in FQCNs kills both URIs and XML
150
                $relClass = $this->sanitiseClassName($relClass);
151
152
                // Field filtering
153
                if ($fields && !in_array($relName, $fields)) {
154
                    continue;
155
                }
156
                if ($this->customRelations && !in_array($relName, $this->customRelations)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRelations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
157
                    continue;
158
                }
159
160
                $xml .= "<$relName linktype=\"many_many\" href=\"$objHref/$relName.xml\">\n";
161
                $items = $obj->$relName();
162
                if ($items) {
163
                    foreach ($items as $item) {
164
                        $href = Director::absoluteURL($this->config()->api_base . "$relClass/$item->ID");
165
                        $xml .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\"></$relClass>\n";
166
                    }
167
                }
168
                $xml .= "</$relName>\n";
169
            }
170
        }
171
172
        $xml .= "</$className>";
173
174
        return $xml;
175
    }
176
177
    /**
178
     * Generate an XML representation of the given {@link SS_List}.
179
     *
180
     * @param SS_List $set
181
     * @return String XML
182
     */
183
    public function convertDataObjectSet(SS_List $set, $fields = null)
184
    {
185
        Controller::curr()->getResponse()->addHeader("Content-Type", "text/xml");
186
        $className = $this->sanitiseClassName(get_class($set));
187
188
        $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
189
        $xml .= (is_numeric($this->totalSize)) ? "<$className totalSize=\"{$this->totalSize}\">\n" : "<$className>\n";
190
        foreach ($set as $item) {
191
            $xml .= $this->convertDataObjectWithoutHeader($item, $fields);
192
        }
193
        $xml .= "</$className>";
194
195
        return $xml;
196
    }
197
198
    public function convertStringToArray($strData)
199
    {
200
        return Convert::xml2array($strData);
201
    }
202
}
203