Completed
Push — master ( 7cb054...584ba8 )
by Tim
44:11 queued 26:38
created

XMLGenerator   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 426
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 426
rs 8.4864
wmc 48
lcom 1
cbo 5

How to fix   Complexity   

Complex Class

Complex classes like XMLGenerator 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 XMLGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright (C) 2013-2015
4
 * Piotr Olaszewski <[email protected]>
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
namespace WSDL\XML;
25
26
use DOMDocument;
27
use ReflectionClass;
28
use WSDL\Parser\MethodParser;
29
use WSDL\Utilities\Strings;
30
use WSDL\XML\Styles\DocumentLiteralWrapped;
31
use WSDL\XML\Styles\Style;
32
use WSDL\XML\Styles\TypesComplex;
33
use WSDL\XML\Styles\TypesElement;
34
35
/**
36
 * XMLGenerator
37
 *
38
 * @author Piotr Olaszewski <[email protected]>
39
 * @see http://www.xfront.com/GlobalVersusLocal.html
40
 */
41
class XMLGenerator
42
{
43
    private $_name;
44
    private $_location;
45
    private $_targetNamespace;
46
    private $_targetNamespaceTypes;
47
    /**
48
     * @var DOMDocument
49
     */
50
    private $_DOMDocument;
51
    /**
52
     * @var DOMDocument
53
     */
54
    private $_definitionsRootNode;
55
    /**
56
     * @var DOMDocument
57
     */
58
    private $_generatedXML;
59
    /**
60
     * @var MethodParser[]
61
     */
62
    private $_WSDLMethods;
63
    /**
64
     * @var Style
65
     */
66
    private $_bindingStyle;
67
68
    public static $alreadyGeneratedComplexTypes = array();
69
70
    public function __construct($name, $namespace, $location)
71
    {
72
        $this->_name = $this->extractClassName($name);
73
        $this->_location = $location;
74
75
        $this->_targetNamespace = $this->sanitizeClassName($namespace, $name);
76
        $this->_targetNamespaceTypes = $this->_targetNamespace . '/types';
77
78
        $this->_DOMDocument = new DOMDocument("1.0", "UTF-8");
79
        $this->_DOMDocument->formatOutput = true;
80
        $this->_saveXML();
81
    }
82
83
    public function sanitizeClassName($namespace, $class)
84
    {
85
        return Strings::sanitizedNamespaceWithClass($namespace, $class);
86
    }
87
88
    public function extractClassName($name)
89
    {
90
        $reflectedClass = new ReflectionClass($name);
91
        return $reflectedClass->getShortName();
92
    }
93
94
    public function setWSDLMethods($WSDLMethods)
95
    {
96
        $this->_WSDLMethods = $WSDLMethods;
97
        return $this;
98
    }
99
100
    public function setBindingStyle(Style $_bindingStyle)
101
    {
102
        $this->_bindingStyle = $_bindingStyle;
103
        return $this;
104
    }
105
106
    public function generate()
107
    {
108
        $this->_definitions()
109
            ->_types()
110
            ->_message()->_portType()->_binding()->_service();
111
    }
112
113
    /**
114
     * @return XMLGenerator
115
     */
116
    private function _definitions()
117
    {
118
        $definitionsElement = $this->createElementWithAttributes('definitions', array(
119
            'name' => $this->_name,
120
            'targetNamespace' => $this->_targetNamespace,
121
            'xmlns:tns' => $this->_targetNamespace,
122
            'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
123
            'xmlns:soap' => 'http://schemas.xmlsoap.org/wsdl/soap/',
124
            'xmlns:soapenc' => "http://schemas.xmlsoap.org/soap/encoding/",
125
            'xmlns' => 'http://schemas.xmlsoap.org/wsdl/',
126
            'xmlns:ns' => $this->_targetNamespaceTypes
127
        ));
128
        $this->_DOMDocument->appendChild($definitionsElement);
129
        $this->_definitionsRootNode = $definitionsElement;
130
        $this->_saveXML();
131
        return $this;
132
    }
133
134
    /**
135
     * @return XMLGenerator
136
     */
137
    private function _types()
138
    {
139
        $typesElement = $this->_createElement('types');
140
141
        $schemaElement = $this->createElementWithAttributes('xsd:schema', array(
142
            'targetNamespace' => $this->_targetNamespaceTypes,
143
            'xmlns' => $this->_targetNamespaceTypes
144
        ));
145
146
        foreach ($this->_WSDLMethods as $method) {
147
            $typeParameters = $this->_bindingStyle->typeParameters($method);
148
            if ($typeParameters) {
149
                $this->_typesParameters($typeParameters, $schemaElement);
150
            }
151
152
            $typeReturning = $this->_bindingStyle->typeReturning($method);
153
            $this->_generateComplexType($typeReturning, $schemaElement);
154
        }
155
156
        $typesElement->appendChild($schemaElement);
157
        $this->_definitionsRootNode->appendChild($typesElement);
158
        $this->_saveXML();
159
        return $this;
160
    }
161
162
    private function _typesParameters($parameters, $schemaElement)
163
    {
164
        foreach ($parameters as $parameter) {
165
            $this->_generateComplexType($parameter, $schemaElement);
166
        }
167
    }
168
169
    private function _generateComplexType($parameter, $schemaElement)
170
    {
171
        if ($parameter instanceof TypesComplex) {
172
            if (!$this->_bindingStyle instanceof DocumentLiteralWrapped) {
173
                $this->_generateArray($parameter, $schemaElement);
174
            } else {
175
                $this->_generateTypedArray($parameter, $schemaElement);
176
            }
177
        }
178
        if ($parameter instanceof TypesElement) {
179
            $this->_generateObject($parameter, $schemaElement);
180
        }
181
    }
182
183
    private function _generateObject(TypesElement $parameter, $schemaElement)
184
    {
185
        $name = $parameter->getName();
186
187
        if (self::isAlreadyGenerated($name)) {
188
            return;
189
        }
190
191
        $element = $this->createElementWithAttributes('xsd:element', array(
192
            'name' => $name,
193
            'nillable' => 'true',
194
            'type' => 'ns:' . $name
195
        ));
196
        $complexTypeElement = $this->createElementWithAttributes('xsd:complexType', array(
197
            'name' => $name
198
        ));
199
        $sequenceElement = $this->_createElement('xsd:sequence');
200
201
        $types = $parameter->getElementAttributes();
202
        foreach ($types as $complexType) {
203
            $elementPartElement = $this->createElementWithAttributes('xsd:element', array(
204
                'name' => $complexType['name'],
205
                $complexType['type'] => $complexType['value']
206
            ));
207
            $sequenceElement->appendChild($elementPartElement);
208
        }
209
210
        $complex = $parameter->getComplex();
211
        if ($complex) {
212
            foreach ($complex as $complexElement) {
213
                $this->_generateComplexType($complexElement, $schemaElement);
214
            }
215
        }
216
217
        $complexTypeElement->appendChild($sequenceElement);
218
219
        $schemaElement->appendChild($complexTypeElement);
220
        $schemaElement->appendChild($element);
221
    }
222
223
    private function _generateArray(TypesComplex $parameter, $schemaElement)
224
    {
225
        $name = $parameter->getName();
226
        $type = $parameter->getArrayType();
227
228
        if (self::isAlreadyGenerated($name)) {
229
            return;
230
        }
231
232
        $complexTypeElement = $this->createElementWithAttributes('xsd:complexType', array('name' => $name));
233
        $complexContentElement = $this->_createElement('xsd:complexContent');
234
        $restrictionElement = $this->createElementWithAttributes('xsd:restriction', array('base' => 'soapenc:Array'));
235
        $attributeElement = $this->createElementWithAttributes('xsd:attribute', array(
236
            'ref' => 'soapenc:arrayType',
237
            'soap:arrayType' => $type
238
        ));
239
        $restrictionElement->appendChild($attributeElement);
240
        $complexContentElement->appendChild($restrictionElement);
241
        $complexTypeElement->appendChild($complexContentElement);
242
        $schemaElement->appendChild($complexTypeElement);
243
244
        if ($parameter->getComplex()) {
245
            $this->_generateComplexType($parameter->getComplex(), $schemaElement);
246
        }
247
    }
248
249
    private function _generateTypedArray(TypesComplex $parameter, $schemaElement)
250
    {
251
        $name = $parameter->getName();
252
        $type = $parameter->getArrayType();
253
        $typeName = $parameter->getArrayTypeName();
254
255
        if (self::isAlreadyGenerated($name)) {
256
            return;
257
        }
258
259
        $complexTypeElement = $this->createElementWithAttributes('xsd:complexType', array('name' => $name));
260
        $sequence = $this->_createElement('xsd:sequence');
261
        $element = $this->createElementWithAttributes('xsd:element', array(
262
            'minOccurs' => 0,
263
            'maxOccurs' => 'unbounded',
264
            'name' => $typeName,
265
            'nillable' => 'true',
266
            'type' => str_replace('[]', '', $type)
267
        ));
268
        $sequence->appendChild($element);
269
        $complexTypeElement->appendChild($sequence);
270
        $schemaElement->appendChild($complexTypeElement);
271
272
        if ($parameter->getComplex()) {
273
            $this->_generateComplexType($parameter->getComplex(), $schemaElement);
274
        }
275
    }
276
277
    public static function isAlreadyGenerated($name)
278
    {
279
        if (in_array($name, self::$alreadyGeneratedComplexTypes)) {
280
            return true;
281
        } else {
282
            self::$alreadyGeneratedComplexTypes[] = $name;
283
            return false;
284
        }
285
    }
286
287
    /**
288
     * @return XMLGenerator
289
     */
290
    private function _message()
291
    {
292
        foreach ($this->_WSDLMethods as $method) {
293
            $messageInputElement = $this->_messageInput($method);
294
            $this->_definitionsRootNode->appendChild($messageInputElement);
295
296
            $messageOutputElement = $this->_messageOutput($method);
297
            $this->_definitionsRootNode->appendChild($messageOutputElement);
298
        }
299
        return $this;
300
    }
301
302
    private function _messageInput(MethodParser $method)
303
    {
304
        $messageInputElement = $this->createElementWithAttributes('message', array(
305
            'name' => $method->getName() . 'Request'
306
        ));
307
        $partsInput = $this->_bindingStyle->methodInput($method);
308
        $obj = $this;
309
        $partsInput = array_map(function ($attributes) use ($obj) {
310
            return $obj->createElementWithAttributes('part', $attributes);
311
        }, $partsInput);
312
        foreach ($partsInput as $part) {
313
            $messageInputElement->appendChild($part);
314
        }
315
        return $messageInputElement;
316
    }
317
318
    private function _messageOutput(MethodParser $method)
319
    {
320
        $messageOutputElement = $this->createElementWithAttributes('message', array(
321
            'name' => $method->getName() . 'Response'
322
        ));
323
        $partsOutput = $this->_bindingStyle->methodOutput($method);
324
        $partsOutput = $this->createElementWithAttributes('part', $partsOutput);
325
        $messageOutputElement->appendChild($partsOutput);
326
        return $messageOutputElement;
327
    }
328
329
    /**
330
     * @return XMLGenerator
331
     */
332
    private function _portType()
333
    {
334
        $portTypeElement = $this->createElementWithAttributes('portType', array(
335
            'name' => $this->_name . 'PortType'
336
        ));
337
338
        foreach ($this->_WSDLMethods as $method) {
339
            $operationElement = $this->createElementWithAttributes('operation', array('name' => $method->getName()));
340
341
            if ($method->description()) {
342
                $documentationElement = $this->_createElement('documentation', $method->description());
343
                $operationElement->appendChild($documentationElement);
344
            }
345
346
            $inputElement = $this->createElementWithAttributes('input', array('message' => 'tns:' . $method->getName() . 'Request'));
347
            $operationElement->appendChild($inputElement);
348
349
            $outputElement = $this->createElementWithAttributes('output', array('message' => 'tns:' . $method->getName() . 'Response'));
350
            $operationElement->appendChild($outputElement);
351
352
            $portTypeElement->appendChild($operationElement);
353
        }
354
        $this->_definitionsRootNode->appendChild($portTypeElement);
355
        $this->_saveXML();
356
        return $this;
357
    }
358
359
    /**
360
     * @return XMLGenerator
361
     */
362
    private function _binding()
363
    {
364
        $bindingElement = $this->createElementWithAttributes('binding', array(
365
            'name' => $this->_name . 'Binding',
366
            'type' => 'tns:' . $this->_name . 'PortType'
367
        ));
368
369
        $soapBindingElement = $this->createElementWithAttributes('soap:binding', array(
370
            'style' => $this->_bindingStyle->bindingStyle(),
371
            'transport' => 'http://schemas.xmlsoap.org/soap/http'
372
        ));
373
        $bindingElement->appendChild($soapBindingElement);
374
375
        foreach ($this->_WSDLMethods as $method) {
376
            $soapBodyElement = $this->createElementWithAttributes('soap:body', array(
377
                'use' => $this->_bindingStyle->bindingUse(),
378
                'namespace' => $this->_targetNamespace
379
            ));
380
381
            if ($this->_bindingStyle instanceof \WSDL\XML\Styles\RpcEncoded) {
382
                $encodingUri = $this->_createAttributeWithValue('encodingStyle', 'http://schemas.xmlsoap.org/soap/encoding/');
383
                $soapBodyElement->appendChild($encodingUri);
384
            }
385
386
            $operationElement = $this->createElementWithAttributes('operation', array(
387
                'name' => $method->getName()
388
            ));
389
390
            $soapOperationElement = $this->createElementWithAttributes('soap:operation', array(
391
                'soapAction' => $this->_targetNamespace . '/#' . $method->getName()
392
            ));
393
            $operationElement->appendChild($soapOperationElement);
394
395
            $inputElement = $this->_createElement('input');
396
            $inputElement->appendChild($soapBodyElement);
397
            $operationElement->appendChild($inputElement);
398
399
            $outputElement = $this->_createElement('output');
400
            $outputElement->appendChild($soapBodyElement->cloneNode());
401
            $operationElement->appendChild($outputElement);
402
403
            $bindingElement->appendChild($operationElement);
404
        }
405
        $this->_definitionsRootNode->appendChild($bindingElement);
406
        $this->_saveXML();
407
        return $this;
408
    }
409
410
    /**
411
     * @return XMLGenerator
412
     */
413
    private function _service()
414
    {
415
        $serviceElement = $this->createElementWithAttributes('service', array('name' => $this->_name . 'Service'));
416
417
        $portElement = $this->createElementWithAttributes('port', array(
418
            'name' => $this->_name . 'Port',
419
            'binding' => 'tns:' . $this->_name . 'Binding'
420
        ));
421
422
        $soapAddressElement = $this->createElementWithAttributes('soap:address', array('location' => $this->_location));
423
        $portElement->appendChild($soapAddressElement);
424
425
        $serviceElement->appendChild($portElement);
426
        $this->_definitionsRootNode->appendChild($serviceElement);
427
        $this->_saveXML();
428
    }
429
430
    private function _createElement($elementName, $value = '')
431
    {
432
        return $this->_DOMDocument->createElement($elementName, $value);
433
    }
434
435
    private function _createAttributeWithValue($attributeName, $value)
436
    {
437
        $attribute = $this->_DOMDocument->createAttribute($attributeName);
438
        $attribute->value = $value;
439
        return $attribute;
440
    }
441
442
    public function createElementWithAttributes($elementName, $attributes, $value = '')
443
    {
444
        $element = $this->_createElement($elementName, $value);
445
        foreach ($attributes as $attributeName => $attributeValue) {
446
            $tmpAttr = $this->_createAttributeWithValue($attributeName, $attributeValue);
447
            $element->appendChild($tmpAttr);
448
        }
449
        return $element;
450
    }
451
452
    private function _saveXML()
453
    {
454
        $this->_generatedXML = $this->_DOMDocument->saveXML();
455
    }
456
457
    public function getGeneratedXML()
458
    {
459
        return $this->_generatedXML;
460
    }
461
462
    public function render()
463
    {
464
        echo $this->_generatedXML;
465
    }
466
}
467