Integrating SOAP/XML APIs in Laravel without losing your mind
SOAP is not dead — it's just hiding in every enterprise ERP system you'll ever have to integrate with. A practical guide to building reliable, debuggable SOAP integrations in Laravel, including error handling, response parsing, and keeping the rest of your codebase clean.
Despite the prevalence of RESTful design patterns, SOAP and legacy XML APIs remain widely deployed inside banking backends and corporate ERP systems. Integrating these systems requires strict custom wrappers to prevent blocking operations and manage inconsistent schema configurations.
Extending the Native Client
PHP's default SoapClient is fragile. Under network drops or backend hangs, it blocks PHP workers indefinitely because it lacks sensible default timeouts. We must extend the client class to implement explicit cURL timeouts and capture raw XML payloads for audit logging.
namespace App\Services\Soap;
use SoapClient;
class CustomSoapClient extends SoapClient
{
private int $timeout = 10; // Explicit timeout in seconds
public function __doRequest($request, $location, $action, $version, $one_way = 0): string
{
$ch = curl_init($location);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: ' . $action
]);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new \Exception('SOAP Connection failed: ' . curl_error($ch));
}
curl_close($ch);
return $response;
}
}Safe Parsing and Error Handling
XML parsing is a frequent source of security vulnerabilities (like XML External Entity attacks). Disable external entity loading explicitly in your parse logic and convert payloads to secure, iterable PHP collections or arrays.
namespace App\Services\Soap;
use SimpleXMLElement;
class XmlParser
{
public static function safeParse(string $rawXml): array
{
// Prevent XXE attacks by disabling entity loader
$previous = libxml_disable_entity_loader(true);
libxml_use_internal_errors(true);
try {
$xml = new SimpleXMLElement($rawXml);
// Convert simpleXML to associative array
$data = json_decode(json_encode($xml), true);
libxml_disable_entity_loader($previous);
return $data;
} catch (\Exception $e) {
libxml_clear_errors();
libxml_disable_entity_loader($previous);
throw new \Exception('Malformed XML response: ' . $e->getMessage());
}
}
}The Integration Architecture
Avoid calling these legacy endpoints synchronously inside HTTP request cycles. Instead, run integrations inside isolated background queues with a sensible retry strategy and dead-letter queues to catch total API dropouts.
namespace App\Jobs;
use App\Services\Soap\CustomSoapClient;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SyncERPInventory implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3; // Allow up to 3 execution attempts
public int $backoff = 15; // Wait 15 seconds between retries
public function handle(): void
{
$client = new CustomSoapClient(config('services.erp.wsdl'), [
'trace' => true,
'exceptions' => true
]);
$response = $client->GetInventory(['WarehouseID' => 101]);
// Process data in db transactions...
}
}Sagar builds operational systems and developer hosting infrastructure from the ground up, specializing in Linux, PHP, and high-performance architectures.