WsdlToPhp / PackageGenerator

Generates a PHP SDK based on a WSDL, simple and powerful, WSDL to PHP
https://providr.io
MIT License
428 stars 73 forks source link

Feature: generate one package from few wsdl #220

Open 4n70w4 opened 4 years ago

4n70w4 commented 4 years ago

Hi! One vendor provides its services as microservices. Approximately 20 microservices with 1-2 methods. Separate wsdl for each service.

It doesn't seem like a good idea to generate 20 packages for each service and include 20 dependencies.

I would like to be able to generate 1 package from an unlimited number of wsdl.

mikaelcom commented 4 years ago

My point of view is that you can generate the packages in the same directory with the same main namespace then namespace each microservice under the main namespace such as:

|_ src/
|__ MyMicroServices/
|___ MicroService1/
|____ StructType/
|____ ServiceType
|_____ Call.php
|_____ Get.php
|_____ etc.
|____ ClassMap.php
|___ MicroService2/
|____ StructType/
|____ ServiceType
|_____ Set.php
|_____ Do.php
|_____ Set.php
|_____ Update.php
|_____ etc.
|____ ClassMap.php
|___ etc.
|_ vendor/
|_ composer.json

Would it match your needs?

4n70w4 commented 4 years ago

In this case, I have to run the generate:package command 20 times with a different set of parameters. And also run 20 commands when updating services. This can be solved through bash scripts or ansible.

And also manually edit composer.json when adding or removing microservices. But I have no ideas how to automate it.

mikaelcom commented 4 years ago

In this case, I have to run the generate:package command 20 times with a different set of parameters. And also run 20 commands when updating services. This can be solved through bash scripts or ansible.

And also manually edit composer.json when adding or removing microservices. But I have no ideas how to automate it.

Indeed, this can seem overaded but this is the way as it is now.

You composer.json file does not have to change for each update as only the main namespace, MyMicroServices, has to be defined within it as every package should be a sub namespace MyMicroServices\MicroService1, MyMicroServices\MicroService2, etc...

4n70w4 commented 4 years ago

I still do not understand with what parameters the generator should be called to get such a structure.

4n70w4 commented 4 years ago

My usercase. I generate packages via wsdltophp and publish them as is in my selfhosted bitbucket server. Next I require those packages in projects via composer.

With a bunch of microservices, the problem of creating many small repositories, generating packages, publishing, and require. Too much fuss.

It would be very convenient to be able to generate one large package with several such microservices.

mikaelcom commented 4 years ago

From my point of view, you would need to create one git project containing all the generated package under the same namespace and then require this unique dependency from your other project.

I currently do this soort of thing using a PHP script (also possible with a command line script):

$rootDir = dirname(__DIR__).'/public_html';

require_once $rootDir.'/vendor/autoload.php';

use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

// define all my WSDL and their package destination
$wsdls = [
    $rootDir.'/wsdl/provider1/service1/service.wsdl' => [
        'src' => $rootDir.'/src/ProviderService/Provider1/Service1/',
        'ns' => 'ProviderService\Provider\Service1',
    ],
    $rootDir.'/wsdl/provider1/service2/service.wsdl' => [
        'src' => $rootDir.'/src/ProviderService/Provider1/Service2/',
        'ns' => 'ProviderService\Provider\Service2',
    ],
    $rootDir.'/wsdl/provider1/service3/service.wsdl' => [
        'src' => $rootDir.'/src/ProviderService/Provider1/Service3/',
        'ns' => 'ProviderService\Provider\Service3',
    ],
    $rootDir.'/wsdl/provider2/service1/service.wsdl' => [
        'src' => $rootDir.'/src/ProviderService/Provider2/Service1/',
        'ns' => 'ProviderService\Provider2\Service1',
    ],
    // etc.
];

// clean existing files, avoid having not useful files anymore
exec('rm -rf '.$rootDir.'/src/ProviderService/Provider1/Service1/');
exec('rm -rf '.$rootDir.'/src/ProviderService/Provider1/Service2/');
exec('rm -rf '.$rootDir.'/src/ProviderService/Provider1/Service3/');
exec('rm -rf '.$rootDir.'/src/ProviderService/Provider2/Service1/');

// loop over each WSDL and generate its package
foreach ($wsdls as $wsdl => $settings) {
    // Options definition: the configuration file parameter is optional
    $options = GeneratorOptions::instance();
    $options
        ->setOrigin($wsdl)
        ->setDestination($settings['src'])
        ->setNamespace($settings['ns'])
        // ->setSoapClientClass(SoapClientBase::class)
        ->setStandalone(false) // no composer.json needed
        ->setGenerateTutorialFile(false) // no tutorial.php filke needed
        ->setSrcDirname(''); // no src folder needed for the generated package
    // Generator instanciation
    $generator = new Generator($options);
    // Package generation
    $generator->generatePackage();
    fwrite(STDERR, PHP_EOL.$settings['src'].' / '.$settings['ns']);
}

Within the composer.json file, I put:

"autoload": {
        "psr-4": {
            "ProviderService\\": "src/ProviderService"
        }
    },

I then use any service from the ProviderService namespace.

4n70w4 commented 4 years ago

@mikaelcom thank! This way looks good.

gugglegum commented 1 year ago

I made my own fully automated solution to generate the final composer package, ready to commit, without any manual corrections. Hope this will help to someone:

<?php

require __DIR__ . '/vendor/autoload.php';

use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

$destination = __DIR__ . '/../your-company-wsdl';  // replace it with the directory of generated package
$namespace = 'Your\Company\Wsdl';         // replace it with your namespace
$package = 'username/your-company-wsdl';  // replace it with the Composer package name
$services = [ // Replace it with your list of service names and WSDL URLs
    'Inventory2' => 'https://********.com/wsdl/inventory2.0.0RC4/InventoryService.wsdl',
    'Invoice' => 'https://********.com/wsdl/InvoiceService.wsdl',
    'PurchaseOrder' => 'https://********.com/wsdl/POService.wsdl',
    'OrderShipmentNotification' => 'https://********.com/wsdl/OrderShipmentNotificationService.wsdl',
    'OrderStatus' => 'https://********.com/wsdl/OrderStatusService.wsdl',
    'PricingAndConfiguration' => 'https://********.com/wsdl/PricingAndConfiguration.wsdl',
    'ProductData' => 'https://********.com/wsdl/ProductDataService.wsdl',
];

$options = GeneratorOptions::instance();
$options->setDestination($destination)
    ->setOptionValue(GeneratorOptions::COMPOSER_SETTINGS, [
        'description' => 'Your Company WSDL Client',
        'require' => [ // replace it with PHP version you need or add some dependencies if your package contains manually created classes
            'php' => '>=8.0',
        ],
    ])
    ->setNamespaceDictatesDirectories(false)
    ->setComposerName($package)
    ->setGenerateTutorialFile(false);

$composer = [];
foreach ($services as $serviceName => $wsdlUrl) {
    echo "\nGenerate {$serviceName}\n\n";
    $options->setOrigin($wsdlUrl)
        ->setSrcDirname("src/{$serviceName}")
        ->setNamespace($namespace . '\\' . $serviceName);
    $generator = new Generator($options);
    $generator->generatePackage();
    $composer = array_replace_recursive($composer, json_decode(file_get_contents($destination . '/composer.json'), true));
}

echo "\nFixing autoload in composer.json\n\n";
$composer['autoload']['psr-4'] = [
    $namespace . '\\' => "src/",
];
file_put_contents($destination . '/composer.json', json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n");

echo "Well done!\n";

It will produce the structure like:

src
  Inventory2
    ...
  Invoice
    ...
  OrderShipmentNotification
    ...
  OrderStatus
    ...
  PricingAndConfiguration
    ...
  ProductData
    ...
  PurchaseOrder
    ...
vendor
composer.json
composer.lock

The composer.json file will look like:

{
    "name": "username/your-company-wsdl",
    "description": "Your Company WSDL Client",
    "require": {
        "php": ">=8.0",
        "ext-dom": "*",
        "ext-mbstring": "*",
        "ext-soap": "*",
        "wsdltophp/packagebase": "~5.0"
    },
    "autoload": {
        "psr-4": {
            "Your\\Company\\Wsdl\\": "src/"
        }
    }
}

Please tell me what do you think about my solution. I'm looking forward to comments.

gugglegum commented 1 year ago

I reworked my previous solution for automatic generation of the Composer package with a multiple WSDL services. It works exactly the same and makes the same folder and namespace structure. But now the generation script is a part of this package. It makes easier to regenerate the code if something changed in the future, because no need to search the script that generated this package maybe several years ago.

You start creating Composer package with running this command in an empty folder of new Composer package:

composer require --dev wsdltophp/packagegenerator

It will make a simplest composer.json and install necessary vendor packages. Then place this PHP script in the root of this Composer package and execute:

<?php

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Console\Input\ArrayInput;
use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

$destination = __DIR__;
$packageName = 'username/your-company-wsdl';
$packageDescription = 'Your Company WSDL Client';
$namespace = 'Your\Company\Wsdl';

// List of WSDL services in result Composer package [ 'Source-SubFolder' => 'WSDL-URL', ... ]
$services = [
    'Inventory2' => 'https://********.com/wsdl/inventory2.0.0RC4/InventoryService.wsdl',
    'Invoice' => 'https://********.com/wsdl/InvoiceService.wsdl',
    'PurchaseOrder' => 'https://********.com/wsdl/POService.wsdl',
    'OrderShipmentNotification' => 'https://********.com/wsdl/OrderShipmentNotificationService.wsdl',
    'OrderStatus' => 'https://********.com/wsdl/OrderStatusService.wsdl',
    'PricingAndConfiguration' => 'https://********.com/wsdl/PricingAndConfiguration.wsdl',
    'ProductData' => 'https://********.com/wsdl/ProductDataService.wsdl',
];

$options = (GeneratorOptions::instance())
    ->setDestination($destination)
    ->setNamespaceDictatesDirectories(false)
    ->setGenerateTutorialFile(false)
    ->setStandalone(false);

foreach ($services as $serviceName => $wsdlUrl) {
    echo "Generate {$serviceName}\n";
    $options->setOrigin($wsdlUrl)
        ->setSrcDirname("src/{$serviceName}")
        ->setNamespace($namespace . '\\' . $serviceName);
    $generator = new Generator($options);
    $generator->generatePackage();
}

echo "Write composer.json\n";
$composer = [
    'name' => $packageName,
    'description' => $packageDescription,
    'require' => [ // replace it with PHP version you need or add some dependencies if your package contains manually created classes
        'php' => '>=8.0',
        'ext-dom' => '*',
        'ext-mbstring' => '*',
        'ext-soap' => '*',
        'ext-ctype' => '*',
        'wsdltophp/packagebase' => '~5.0',
    ],
    'require-dev' => [
        'wsdltophp/packagegenerator' => '^4.1',
    ],
    'autoload' => [
        'psr-4' => [
            $namespace . '\\' => 'src/',
        ],
    ],
];
file_put_contents($destination . '/composer.json', json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");

echo "Execute \"composer update\"\n";
$composer = new \Composer\Console\Application();
$composer->setAutoExit(false);
$composer->run(new ArrayInput([
    'command' => 'update',
    '--optimize-autoloader' => true,
    '--working-dir' => $destination,
]));

echo "Well done!\n";

Now your package is ready to commit and publish. Later you can run this script again to make sure all is up-to-date.