venture-oak-labs/wsdl2php-generator-cli
A command-line tool that generates PHP classes from WSDL files, streamlining SOAP client development. It wraps the wsdl2php-generator package in a ready-to-use CLI for faster integration and less boilerplate.
This package is a thin CLI wrapper around Wsdl2PhpGenerator. Treat it as a development-time code generator, not a runtime SOAP abstraction. In Laravel, the usual flow is: generate PHP classes from a WSDL, commit the generated code, wrap it in a small application-owned adapter, and bind that adapter through the container.
Minimal starting steps:
Inspect the CLI surface first. Check the package README and composer.json bin section to confirm the executable name, then run help before generating:
composer require --dev venture-oak-labs/wsdl2php-generator-cli
vendor/bin/wsdl2php --help
The binary may be named differently; use the bin entry from composer.json if wsdl2php is not present.
Confirm runtime requirements. You need PHP with ext-soap enabled, and the generated classes will depend on SoapClient. Add ext-soap to composer.json platform requirements if the application will call the generated client at runtime.
Generate into an application-owned directory. Prefer a dedicated path such as app/Services/Soap/Generated or app/Integrations/Acme/Soap/Generated. If the CLI exposes namespace/output options, use them so the files fit PSR-4:
vendor/bin/wsdl2php generate \
--input=resources/wsdl/acme.wsdl \
--output=app/Services/Soap/Generated \
--namespace="App\\Services\\Soap\\Generated"
Flag names vary by wrapper version. Use --help and the underlying Wsdl2PhpGenerator documentation for the real option set.
Run composer dump-autoload after generation if the output directory is new or outside the default PSR-4 map.
Find the generated entry class. Open the generated directory and identify the class extending SoapClient or representing the service port. Inspect its constructor and public methods; these map to WSDL operations.
First practical use case: pick one low-risk operation, instantiate the generated client in php artisan tinker with the WSDL URL and explicit SOAP options, call the operation, inspect the response, then wrap that call in an application service before using it elsewhere.
$client = new \App\Services\Soap\Generated\AcmeService([
'location' => config('services.acme.endpoint'),
'trace' => true,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
]);
$result = $client->someOperation($request);
Do not spread direct generated-client usage across controllers and jobs. Put it behind an adapter immediately.
Commit generated code and make regeneration explicit. The generator is a dev dependency; generated classes are application code. Commit them so production deploys do not need the generator installed. Add a Composer script or Artisan command for repeatable regeneration:
"scripts": {
"soap:generate": [
"@php vendor/bin/wsdl2php generate --input=resources/wsdl/acme.wsdl --output=app/Services/Soap/Generated"
]
}
If the CLI flags are insufficient, wrap the underlying Wsdl2PhpGenerator library in a small custom Artisan command. That is the main extension point: the CLI is convenience, not a full configuration layer.
Keep WSDL files in the repository. Store stable WSDL/XSD files under resources/wsdl/ and use local paths for generation. Remote WSDL URLs are convenient for discovery but make builds non-deterministic and can break on network access, authentication, or imported schemas.
Use an adapter and service provider. Bind an application-owned interface, not the generated class:
interface AcmeSoapGateway
{
public function submitOrder(SubmitOrderRequest $request): SubmitOrderResponse;
}
The adapter constructs or receives the generated client, maps domain data to generated request objects, catches SoapFault, and converts it to a domain exception. Configure the generated client from config/services.php:
'acme' => [
'wsdl' => env('ACME_WSDL'),
'endpoint' => env('ACME_SOAP_ENDPOINT'),
'timeout' => env('ACME_SOAP_TIMEOUT', 30),
],
Separate generated namespaces per service. SOAP WSDLs often produce generic class names such as Request, Response, Header, or Type. Generate each integration into its own directory and namespace. If namespace support is missing, post-process namespaces or keep one WSDL per output root and avoid mixing services in the same directory.
Treat generated DTOs as boundary objects. Do not leak generated request/response classes into controllers, jobs, or domain services. Map them in the adapter. This keeps the generated code replaceable when the WSDL changes and prevents SOAP-specific naming from spreading through the app.
Test through the adapter, not the generated client. Unit-test your adapter with a fake implementation of AcmeSoapGateway. Integration-test the generated client against a local fixture WSDL or a controlled SOAP stub. Do not hit a live third-party SOAP endpoint in the normal test suite.
Handle debugging deliberately. Enable trace only in local/debug environments. When debugging, log or inspect __getLastRequest() and __getLastResponse(), but sanitize credentials and business data. For endpoint/service discovery, use:
$soap = new SoapClient($wsdl, ['trace' => true]);
dump($soap->__getFunctions());
dump($soap->__getTypes());
Use CI to detect WSDL drift. If generated code is committed, add a CI step that regenerates from the checked-in WSDL and fails if git diff is non-empty. This catches accidental manual edits to generated classes and reminds the team to regenerate after WSDL updates.
The CLI is a thin wrapper with limited options. If you need custom naming, shared types, namespace mapping, or advanced generator configuration, use the underlying Wsdl2PhpGenerator library directly in a custom Artisan command or PHP script. Check the library README rather than assuming every option is exposed by the CLI.
WSDL imports and relative paths are fragile. Remote WSDLs that import XSDs by relative URL often fail depending on the current working directory and network access. Download the full WSDL/XSD set locally, preserve the directory structure, and run generation from the expected working directory.
SOAP WSDL caching causes stale behavior. SoapClient caches WSDL files according to PHP’s soap.wsdl_cache_* settings. In local development use WSDL_CACHE_NONE; in production ensure the cache directory is writable and clear it during deployment when the WSDL changes. A stale cached WSDL can make generated method names and runtime behavior diverge.
Generated code may be old-style or PHP-version-sensitive. Inspect the output for compatibility with your PHP version, especially around dynamic properties, nullable types, array() syntax, and deprecations. Run PHP lint and your static analyser over generated files. If generated code fails under PHP 8+, post-process it or isolate it behind an adapter and pin the generator version.
Autoloading and namespaces are common failure points. If the generator does not namespace output, you will get class collisions or classes outside PSR-4. Either use the namespace option, generate into a classmap-loaded directory, or post-process namespaces. Always run composer dump-autoload after changing output paths.
SOAP faults are not domain exceptions. The generated client will throw SoapFault when exceptions is enabled. Catch it in the adapter, extract faultcode/faultstring/detail as appropriate, and throw an application exception. Do not let raw SoapFault messages leak to users.
Do not generate at runtime from user-supplied WSDL. Generation is a build/development activity. Runtime generation from arbitrary WSDL introduces security, performance, and deployment risk. Generate from trusted, versioned WSDL files.
Be conservative with trace and logging. trace can retain large request/response payloads in memory. Enable it only behind a debug flag, and never log SOAP envelopes containing credentials, tokens, or personal data.
This package is small and lightly maintained. Its value is convenience; the generated code is portable. Keep the generator as a dev dependency, commit the generated classes, and avoid coupling runtime code to the CLI package. If the wrapper becomes a bottleneck, replace it with a small local script using the underlying library without changing application architecture.
How can I help you explore Laravel packages today?