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