http clients and apis
SOAP And XML APIs
Not every API is JSON over REST. Many finance, government, travel, logistics, healthcare, and legacy enterprise systems still use SOAP or XML APIs. A PHP developer does not need to love them, but they must be able to maintain integrations safely.
SOAP usually uses XML envelopes, WSDL contracts, named operations, strict schemas, and XML namespaces. Plain XML APIs may skip SOAP but still require careful parsing, validation, and error handling.
XML basics
XML is structured text with elements, attributes, and namespaces.
<?php
declare(strict_types=1);
$xml = <<<'XML'
<order id="ord_123">
<total currency="GBP">19.99</total>
</order>
XML;
$document = simplexml_load_string($xml);
if ($document === false) {
throw new RuntimeException('Invalid XML.');
}
echo (string) $document['id'] . PHP_EOL;
echo (string) $document->total . ' ' . (string) $document->total['currency'] . PHP_EOL;
// Prints:
// ord_123
// 19.99 GBP
Do not parse XML with regular expressions. Use XML parsers such as SimpleXML, DOMDocument, XMLReader, or a SOAP client.
SOAP envelopes
SOAP wraps requests and responses in an envelope with a body. Real SOAP messages use namespaces heavily.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetOrderResponse xmlns="https://api.example.test/orders">
<OrderId>ord_123</OrderId>
</GetOrderResponse>
</soap:Body>
</soap:Envelope>
Namespaces are part of the element name. If code works only after removing namespaces from the XML string, it is usually hiding the real problem.
<?php
declare(strict_types=1);
$xml = <<<'XML'
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:o="https://api.example.test/orders">
<soap:Body>
<o:GetOrderResponse>
<o:OrderId>ord_123</o:OrderId>
</o:GetOrderResponse>
</soap:Body>
</soap:Envelope>
XML;
$document = simplexml_load_string($xml);
if ($document === false) {
throw new RuntimeException('Invalid SOAP XML.');
}
$soap = $document->children('http://schemas.xmlsoap.org/soap/envelope/');
$body = $soap->Body;
$orders = $body->children('https://api.example.test/orders');
echo (string) $orders->GetOrderResponse->OrderId . PHP_EOL;
// Prints:
// ord_123
WSDL and SoapClient
A WSDL file describes SOAP operations, message types, endpoint URLs, and bindings. PHP has a built-in SoapClient extension that can read WSDL and call operations.
Conceptually, code may look like this:
<?php
declare(strict_types=1);
$options = [
'trace' => true,
'exceptions' => true,
'connection_timeout' => 10,
];
// $client = new SoapClient('https://api.example.test/service.wsdl', $options);
// $response = $client->__soapCall('GetOrder', [['OrderId' => 'ord_123']]);
print_r($options);
// Prints:
// [trace] => 1
// [exceptions] => 1
// [connection_timeout] => 10
The call is commented because it would require a real external service. In project code, check whether the soap PHP extension is installed and whether the WSDL is local, remote, cached, or generated.
Faults and errors
SOAP can return a SOAP Fault even when the HTTP status is not what a JSON API developer expects. Faults have structured fields such as code and message.
<?php
declare(strict_types=1);
function soapFaultSummary(string $code, string $message): array
{
return [
'type' => 'soap_fault',
'code' => $code,
'message' => $message,
];
}
print_r(soapFaultSummary('Client.Authentication', 'Invalid credentials.'));
// Prints:
// [type] => soap_fault
// [code] => Client.Authentication
// [message] => Invalid credentials.
When debugging SOAP, preserve request IDs and safe excerpts of the request or response. Do not log credentials or full personal data.
XML security
XML parsers have security pitfalls. Be careful with external entities, very large documents, and untrusted input. Avoid enabling network access or external entity loading unless a trusted integration explicitly requires it.
For large XML files, prefer streaming parsers such as XMLReader instead of loading the whole document into memory.
What to check
Before moving on, make sure you can:
- Explain why SOAP integrations often depend on WSDL contracts.
- Read simple XML values and attributes.
- Handle namespaces instead of stripping them.
- Recognise where
SoapClientfits in PHP. - Treat SOAP Faults as structured API errors.
- Avoid unsafe XML parsing and excessive logging.
Practice
Practice: Read A SOAP Response
Write a PHP function that extracts an order ID from a namespaced SOAP response.
Requirements
- Use an XML parser rather than string matching.
- Handle the SOAP namespace.
- Handle the order namespace.
- Return a clear result when XML is invalid.
- Return a clear result when the order ID is missing.
- Include examples for valid XML, invalid XML, and missing order ID.
Show solution
This solution navigates namespaces explicitly so the code matches the XML contract.
<?php
declare(strict_types=1);
function orderIdFromSoapResponse(string $xml): array
{
libxml_use_internal_errors(true);
$document = simplexml_load_string($xml);
libxml_clear_errors();
if ($document === false) {
return ['ok' => false, 'message' => 'SOAP response is not valid XML.'];
}
$soap = $document->children('http://schemas.xmlsoap.org/soap/envelope/');
$body = $soap->Body ?? null;
if ($body === null) {
return ['ok' => false, 'message' => 'SOAP body is missing.'];
}
$orders = $body->children('https://api.example.test/orders');
$orderId = trim((string) ($orders->GetOrderResponse->OrderId ?? ''));
if ($orderId === '') {
return ['ok' => false, 'message' => 'OrderId is missing.'];
}
return ['ok' => true, 'order_id' => $orderId];
}
$valid = <<<'XML'
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:o="https://api.example.test/orders">
<soap:Body>
<o:GetOrderResponse><o:OrderId>ord_123</o:OrderId></o:GetOrderResponse>
</soap:Body>
</soap:Envelope>
XML;
$missingOrderId = <<<'XML'
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:o="https://api.example.test/orders">
<soap:Body>
<o:GetOrderResponse />
</soap:Body>
</soap:Envelope>
XML;
$examples = [
orderIdFromSoapResponse($valid),
orderIdFromSoapResponse('<not closed'),
orderIdFromSoapResponse($missingOrderId),
];
foreach ($examples as $result) {
echo ($result['ok'] ? 'ok: ' . $result['order_id'] : 'error: ' . $result['message']) . PHP_EOL;
}
// Prints:
// ok: ord_123
// error: SOAP response is not valid XML.
// error: OrderId is missing.
The important part is that the code treats namespaces as part of the contract. Removing namespaces to make parsing easier usually makes the integration more fragile.