Completed
Pull Request — master (#11)
by Bas
06:18
created

SoapMessageEventSubscriber::addMessage()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 3
nop 1
dl 0
loc 20
ccs 11
cts 11
cp 1
crap 4
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace DMT\Soap\Serializer;
4
5
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
6
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
7
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
8
use JMS\Serializer\Exception\InvalidArgumentException;
9
use JMS\Serializer\Exception\RuntimeException;
10
use JMS\Serializer\SerializationContext;
11
use JMS\Serializer\XmlSerializationVisitor;
12
use Metadata\ClassMetadata;
13
14
/**
15
 * Class SoapMessageEventSubscriber
16
 *
17
 * @package DMT\Soap
18
 */
19
class SoapMessageEventSubscriber implements EventSubscriberInterface, SoapNamespaceInterface
20
{
21
    /**
22
     * {@inheritdoc}
23
     */
24 14
    public static function getSubscribedEvents(): array
25
    {
26
        return [
27
            [
28 14
                'event' => 'serializer.pre_serialize',
29
                'method' => 'addMessage',
30
                'format' => 'soap',
31
            ],
32
            [
33
                'event' => 'serializer.pre_deserialize',
34
                'method' => 'getMessage',
35
                'format' => 'soap',
36
            ],
37
        ];
38
    }
39
40
    /**
41
     * @param PreSerializeEvent $event
42
     */
43 7
    public function addMessage(PreSerializeEvent $event)
44
    {
45
        /** @var SerializationContext $context */
46 7
        $context = $event->getContext();
47
        /** @var XmlSerializationVisitor $visitor */
48 7
        $visitor = $context->getVisitor();
49
50 7
        if ($context->getDepth() === 1 && $visitor->getCurrentNode()->nodeName === 'soap:Body') {
51
            /** @var ClassMetadata $metadata */
52 7
            $metadata = $context->getMetadataFactory()->getMetadataForClass($event->getType()['name']);
53
54 7
            if (!isset($metadata->xmlRootName, $metadata->xmlRootNamespace)) {
55 3
                throw new RuntimeException('Missing XmlRootName or XmlRootNamespace for ' . $event->getType()['name']);
56
            }
57
58 4
            $document = $visitor->getDocument();
59 4
            $message = $document->createElementNS($metadata->xmlRootNamespace, $metadata->xmlRootName);
60
61 4
            $visitor->getCurrentNode()->appendChild($message);
62 4
            $visitor->setCurrentNode($message);
63
        }
64 4
    }
65
66
    /**
67
     * @param PreDeserializeEvent $event
68
     */
69 10
    public function getMessage(PreDeserializeEvent $event)
70
    {
71
        /** @var SerializationContext $context */
72 10
        $context = $event->getContext();
73
74 10
        if ($context->getDepth() === 1) {
75 10
            $element = $this->moveChildNamespacesToEnvelope($event->getData());
76
77 10
            $version = array_search(current($element->getNamespaces()), static::SOAP_NAMESPACES);
78 10
            if (!$version) {
79 1
                throw new InvalidArgumentException('Unsupported SOAP version');
80
            }
81
82 9
            $messages = $element->xpath('*[local-name()="Body"]/*');
83 9
            if (count($messages) === 1) {
84 9
                $element = $messages[0];
85
            }
86
87 9
            if ($element->getName() === 'Fault') {
88 5
                if ($version == static::SOAP_1_1) {
89 2
                    $this->throwSoap11Fault($element);
90
                } else {
91 3
                    $this->throwSoap12Fault($element);
92
                }
93
            }
94
95 4
            $event->setData($element);
96
        }
97 4
    }
98
99
    /**
100
     * Move all underlying namespaces to root element.
101
     *
102
     * @param \SimpleXMLElement $element
103
     *
104
     * @return \SimpleXMLElement
105
     */
106 10
    protected function moveChildNamespacesToEnvelope(\SimpleXMLElement $element): \SimpleXMLElement
107
    {
108 10
        $dom = dom_import_simplexml($element);
109
110 10
        foreach ($element->getNamespaces(true) as $prefix => $namespace) {
111 10
            if (!in_array($namespace, $element->getDocNamespaces())) {
112 7
                $dom->setAttributeNS(
113 7
                    'http://www.w3.org/2000/xmlns/',
114 7
                    $prefix ? 'xmlns:' . $prefix : 'xmlns',
115 10
                    $namespace
116
                );
117
            }
118
        }
119
120 10
        return simplexml_import_dom($dom->ownerDocument);
121
    }
122
123
    /**
124
     * @param \SimpleXMLElement $fault
125
     *
126
     * @throws SoapFaultException
127
     */
128 2
    protected function throwSoap11Fault(\SimpleXMLElement $fault)
129
    {
130 2
        $faultcode = $faultstring = '';
131 2
        $faultactor = $detail = null;
132
133 2
        extract($this->elementToArray($fault), EXTR_IF_EXISTS);
134
135 2
        throw new SoapFaultException($this->removePrefix($faultcode), $faultstring, $faultactor, $detail);
136
    }
137
138
    /**
139
     * @param \SimpleXMLElement $fault
140
     *
141
     * @throws SoapFaultException
142
     */
143 3
    protected function throwSoap12Fault(\SimpleXMLElement $fault)
144
    {
145 3
        $lang = substr(locale_get_default(), 0, 2);
146 3
        $path = '*[local-name()="Reason"]/*[local-name()="Text" and @xml:lang="' . $lang . '"]';
147
148 3
        $messages = $fault->xpath($path);
149 3
        if (count($messages) === 0) {
150 1
            $messages = $fault->xpath('*[local-name()="Reason"]/*[local-name()="Text"]');
151
        }
152
153 3
        $code = array_map(
154 3
            [$this, 'removePrefix'],
155 3
            $fault->xpath('//*[local-name()="Code" or local-name()="Subcode"]/*[local-name()="Value"]')
156
        );
157
158 3
        $node = $fault->xpath('*[local-name()="Node"]')[0] ?? null;
159 3
        $detail = $fault->xpath('*[local-name()="Detail"]')[0] ?? null;
160
161 3
        if ($detail !== null) {
162 3
            $detail = $this->elementToArray($detail);
163
        }
164
165 3
        throw new SoapFaultException(implode('.', $code), $messages[0], $node, $detail);
166
    }
167
168
    /**
169
     * @param \SimpleXMLElement $element
170
     *
171
     * @return array
172
     */
173 5
    protected function elementToArray(\SimpleXMLElement $element): array
174
    {
175 5
        $result = [];
176 5
        foreach ($element->xpath('*') as $node) {
177 5
            if (count($node->xpath('*')) > 0) {
178 2
                $value = $this->elementToArray($node);
179
            } else {
180 5
                $value = strval($node);
181
            }
182 5
            $result = array_merge_recursive($result, [$node->getName() => $value]);
183
        }
184
185 5
        return $result;
186
    }
187
188
    /**
189
     * Remove a prefix from text node.
190
     *
191
     * @param string $value A node value containing a namespace prefix, eg SOAP-ENV:Client.
192
     *
193
     * @return string
194
     */
195 5
    protected function removePrefix(string $value): string
196
    {
197 5
        return preg_replace('~^(.+:)?(.*)$~', "$2", $value);
198
    }
199
}
200