venture-oak-labs/wsdl2php-generator-cli-bundle
Symfony bundle adding console commands for the wsdl2php-generator library, so you can turn WSDL/SOAP definitions into PHP classes from the CLI. Handy for SOAP integrations; requires Symfony Console and the underlying generator package.
Treat this as a build-time code generator, not a runtime dependency. It is a Symfony bundle whose only job is to register a console command that runs wsdl2phpgenerator — the library that turns a WSDL file into PHP SOAP client classes. Laravel does not load Symfony bundles, so in practice you will get more mileage by depending on the underlying generator and wrapping it in an Artisan command than by trying to mount the bundle.
Minimal path:
composer require --dev venture-oak-labs/wsdl2php-generator-cli-bundle (or just wsdl2phpgenerator/wsdl2phpgenerator if you only want the library).vendor/venture-oak-labs/wsdl2php-generator-cli-bundle/ and read the bundle class and its Command/ directory first. That is the whole surface area — there is no config tree, no service aliases, and no Laravel auto-discovery. Note the command name and constructor arguments.php artisan make:command GenerateSoapClient
// app/Console/Commands/GenerateSoapClient.php
protected $signature = 'soap:generate
{wsdl : Path or URL to the WSDL}
{--namespace=App\\Soap\\Generated : Target namespace}
{--out=app/Soap/Generated : Output directory}';
public function handle()
{
$generator = new \Wsdl2PhpGenerator\Generator();
$generator->generate(new \Wsdl2PhpGenerator\Config([
'inputFile' => $this->argument('wsdl'),
'outputDir' => base_path($this->option('out')),
'namespaceName' => $this->option('namespace'),
'constructorParamsDefaultToNull' => true,
]));
}
storage/app/wsdl/, run php artisan soap:generate storage/app/wsdl/vendor.wsdl, commit the generated classes under app/Soap/Generated/. From that point on the generator is irrelevant until the vendor ships a new WSDL.First thing to sanity-check before committing to the approach: the underlying generator's PHP version constraint. It historically lags; on PHP 8.2+ you may need composer require with --ignore-platform-req or a fork.
Generate once, commit, review the diff. The value of this package is the deterministic transformation WSDL → classes. Run it in CI on a branch, diff the output, and open a PR when the vendor WSDL changes. Do not run generation at deploy time — you lose reproducibility and the WSDL URL becomes a production dependency.
Separate the generated layer from your domain. Generated classes are dumb DTOs and a giant Service proxy. Wrap them:
// app/Services/Shipping/ShippingGateway.php
final class ShippingGateway
{
public function __construct(private \App\Soap\Generated\ShippingService $client) {}
public function quote(QuoteRequest $request): Quote
{
try {
$res = $this->client->getQuote($request->toSoap());
} catch (\SoapFault $e) {
throw new ShippingUnavailable($e->getMessage(), previous: $e);
}
return Quote::fromSoap($res);
}
}
Bind the generated client in a service provider with credentials pulled from config:
// app/Providers/SoapServiceProvider.php
$this->app->singleton(ShippingService::class, fn () => new ShippingService([
'location' => config('services.shipping.wsdl'),
'login' => config('services.shipping.user'),
'password' => config('services.shipping.password'),
'trace' => app()->isLocal(), // capture envelopes in dev only
'exceptions' => true,
]));
Drive generation from config, not CLI flags. Keep a config/soap.php with a map of WSDL sources and target namespaces, and have your command iterate over it. That gives you php artisan soap:generate --all for regeneration and one place to look when a namespace or output path drifts.
Testing. Never mock the generated client directly — it is a concrete class with dozens of methods. Instead:
ShippingGateway against a fake HTTP transport if the underlying client supports one, orShippingGateway interface to an in-memory implementation in TestCase, and only exercise the real generated client in a tagged @group integration suite.$client->__getLastRequest() when trace is on) — that is what usually breaks when a WSDL changes.Version pinning. After the first successful generation, record the WSDL hash in a comment or a small app/Soap/Generated/.version file. Your command can refuse to run if the WSDL differs unless --force is passed — this prevents accidental churn from a vendor silently publishing a new WSDL.
bundle.php, no extra.laravel. composer require will succeed but nothing will happen. You either register the Symfony bundle manually (painful, requires a minimal Symfony kernel) or, as above, skip the bundle and use the library. Given 0 dependents and 2 stars, assume no Laravel integration exists or is planned.namespaceName. Run php-cs-fixer or Pint over the output directory as a post-step, or exclude it from style checks entirely — do not hand-fix generated files.app/Soap/Generated/. If you need to extend a generated class, subclass it in a sibling directory and reference the subclass.storage/app/wsdl/, and pass the file path. You get reproducible builds and no CI firewall issues.soap PHP extension for the generated clients to run (not for generation). Check php -m | grep soap in every environment and in your composer.json ext-soap requirement. This trips up containerised setups constantly.trace and exceptions options matter for debugging. With trace => true you get __getLastRequest() / __getLastResponse() on the client — log those envelopes into a dedicated channel when you catch a SoapFault. With exceptions => false (the default) you get silent failures; always set exceptions => true.use the handful of classes you need, or hand-roll a slimmer client with ext-soap directly.tinker or a standalone script where you can dd($e->getTrace()), and validate the WSDL first with xmllint --noout — malformed imports and relative schemaLocation entries are the usual culprits.How can I help you explore Laravel packages today?