Skip to content

Commit

Permalink
Merge pull request #525 from veewee/type-replacer
Browse files Browse the repository at this point in the history
Type replacer
  • Loading branch information
veewee authored Jun 11, 2024
2 parents 3f7dc6a + 2d3969a commit ae2a668
Show file tree
Hide file tree
Showing 25 changed files with 684 additions and 115 deletions.
9 changes: 5 additions & 4 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ return Config::create()
))
```

**Note:** In case you are using a custom configuration class by implementing the `ConfigInterface` : this is not supported anymore.
Since code generation gets complexer and complexer, we decided to make the configuration more strict.

Regenerate classes:

```
Expand Down Expand Up @@ -71,18 +74,16 @@ In case you are using a non-default metadata strategy, you can now configure it
```php
use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\RemoveDuplicateTypesStrategy;
use Phpro\SoapClient\Soap\Metadata\MetadataOptions;

return Config::create()
->setTypeMetadataOptions(
MetadataOptions::empty()->withTypesManipulator(new RemoveDuplicateTypesStrategy())
->setDuplicateTypeIntersectStrategy(
new RemoveDuplicateTypesStrategy()
)
```

[More information about the metadata options can be found here.](/docs/drivers/metadata.md)



# V2 to V3

```bash
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"azjezz/psl": "^2.1",
"laminas/laminas-code": "^4.8.0",
"php-soap/cached-engine": "~0.1",
"php-soap/engine": "^2.9",
"php-soap/cached-engine": "~0.2",
"php-soap/engine": "^2.10.1",
"php-soap/encoding": "~0.2",
"php-soap/psr18-transport": "^1.6",
"php-soap/wsdl-reader": "~0.15",
"php-soap/wsdl-reader": "~0.16",
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/console": "~5.4 || ~6.0 || ~7.0",
Expand Down
61 changes: 57 additions & 4 deletions docs/drivers/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use Phpro\SoapClient\Soap\Metadata\MetadataOptions;

return Config::create()
//...
->setTypeMetadataOptions(
MetadataOptions::empty()->withTypesManipulator(new IntersectDuplicateTypesStrategy())
->setDuplicateTypeIntersectStrategy(
new IntersectDuplicateTypesStrategy()
)
// ...
```
Expand All @@ -41,8 +41,61 @@ use Phpro\SoapClient\Soap\Metadata\MetadataOptions;

return Config::create()
//...
->setTypeMetadataOptions(
MetadataOptions::empty()->withTypesManipulator(new RemoveDuplicateTypesStrategy())
->setDuplicateTypeIntersectStrategy(
new IntersectDuplicateTypesStrategy()
)
// ...
```

### Type replacements

Depending on what XML encoders you configure, you might want to replace some types with other types.
Take following example:

By default, a "date" type from the XSD namespace `http://www.w3.org/2001/XMLSchema` will be converted to a `DateTimeImmutable` object.
However, if you configure an encoder that does not support `DateTimeImmutable`,
you might want to replace it with a `int` type that represents the amount of seconds since the unix epoch.

This can be configured in the [client configuration](/docs/code-generation/configuration.md):

```php
use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacers;

return Config::create()
//...
->setTypeReplacements(
TypeReplacers::defaults()
->add(new MyDateReplacer())
)
// ...
```

The `MyDateReplacer` class should implement the `TypeReplacerInterface` and should return the correct type for the given type.

```php
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacer;
use Soap\Engine\Metadata\Model\XsdType;
use Soap\WsdlReader\Metadata\Predicate\IsOfType;use Soap\Xml\Xmlns;

final class MyDateReplacer implements TypeReplacer
{
public function __invoke(XsdType $xsdType) : XsdType
{
$check = new IsOfType(Xmlns::xsd()->value(), 'date');
if (!$check($xsdType)) {
return $xsdType;
}

return $xsdType->copy('int')->withBaseType('int');
}
}
```

This way, the generated code will use the `int` type instead of the `DateTimeImmutable` type for the `date` type in the XSD.

The TypeReplacers contain a default set of type replacements that are being used to improve the generated code:

* `array` for SOAP 1.1 and 1.2 Arrays
* `object` for SOAP 1.1 Objects
* `array` for Apache Map types
5 changes: 0 additions & 5 deletions spec/Phpro/SoapClient/CodeGenerator/Config/ConfigSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ function it_is_initializable()
$this->shouldHaveType(Config::class);
}

function it_is_a_config_class()
{
$this->shouldImplement(ConfigInterface::class);
}

function it_has_an_engine(Engine $engine)
{
$this->setEngine($engine);
Expand Down
74 changes: 57 additions & 17 deletions src/Phpro/SoapClient/CodeGenerator/Config/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
use Phpro\SoapClient\CodeGenerator\Util\Normalizer;
use Phpro\SoapClient\Exception\InvalidArgumentException;
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\IntersectDuplicateTypesStrategy;
use Phpro\SoapClient\Soap\Metadata\Manipulators\MethodsManipulatorChain;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\ReplaceMethodTypesManipulator;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\ReplaceTypesManipulator;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacer;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacers;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypesManipulatorChain;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypesManipulatorInterface;
use Phpro\SoapClient\Soap\Metadata\MetadataFactory;
use Phpro\SoapClient\Soap\Metadata\MetadataOptions;
use Soap\Engine\Engine;
use Soap\Engine\Metadata\Metadata;

/**
* Class Config
*
* @package Phpro\SoapClient\CodeGenerator\Config
*/
final class Config implements ConfigInterface
final class Config
{
/**
* @var string
Expand Down Expand Up @@ -50,7 +54,11 @@ final class Config implements ConfigInterface
*/
protected $typeDestination = '';

protected MetadataOptions $typeMetadataOptions;
protected TypesManipulatorInterface $duplicateTypeIntersectStrategy;

protected TypeReplacer $typeReplacementStrategy;

protected ?MetadataOptions $metadataOptions;

/**
* @var RuleSetInterface
Expand All @@ -74,12 +82,13 @@ final class Config implements ConfigInterface

public function __construct()
{
$this->typeMetadataOptions = MetadataOptions::empty()->withTypesManipulator(
// Working with duplicate types is hard (see FAQ).
// Therefore, we decided to combine all duplicate types into 1 big intersected type by default instead.
// The resulting type will always be usable, but might contain some additional empty properties.
new IntersectDuplicateTypesStrategy()
);
$this->typeReplacementStrategy = TypeReplacers::defaults();

// Working with duplicate types is hard (see FAQ).
// Therefore, we decided to combine all duplicate types into 1 big intersected type by default instead.
// The resulting type will always be usable, but might contain some additional empty properties.
$this->duplicateTypeIntersectStrategy = new IntersectDuplicateTypesStrategy();

$this->ruleSet = new RuleSet([
new Rules\AssembleRule(new Assembler\PropertyAssembler()),
new Rules\AssembleRule(new Assembler\ClassMapAssembler()),
Expand Down Expand Up @@ -259,18 +268,49 @@ public function setTypeDestination($typeDestination): self
return $this;
}

public function getTypeMetadataOptions(): MetadataOptions
public function getMetadataOptions(): MetadataOptions
{
return $this->metadataOptions ?? MetadataOptions::empty()
->withTypesManipulator(
new TypesManipulatorChain(
$this->duplicateTypeIntersectStrategy,
new ReplaceTypesManipulator($this->typeReplacementStrategy),
)
)->withMethodsManipulator(
new MethodsManipulatorChain(
new ReplaceMethodTypesManipulator($this->typeReplacementStrategy)
)
);
}

public function getManipulatedMetadata(): Metadata
{
return MetadataFactory::manipulated(
$this->getEngine()->getMetadata(),
$this->getMetadataOptions()
);
}

public function setTypeReplacementStrategy(TypeReplacer $typeReplacementStrategy): self
{
return $this->typeMetadataOptions;
$this->typeReplacementStrategy = $typeReplacementStrategy;

return $this;
}

public function setTypeMetadataOptions(MetadataOptions $typeMetadataOptions): self
public function setDuplicateTypeIntersectStrategy(TypesManipulatorInterface $duplicateTypeIntersectStrategy): self
{
$this->typeMetadataOptions = $typeMetadataOptions;
$this->duplicateTypeIntersectStrategy = $duplicateTypeIntersectStrategy;

return $this;
}

public function setMetadataOptions(MetadataOptions $metadataOptions): self
{
$this->metadataOptions = $metadataOptions;

return $this;
}

/**
* @return string
Expand Down
69 changes: 0 additions & 69 deletions src/Phpro/SoapClient/CodeGenerator/Config/ConfigInterface.php

This file was deleted.

1 change: 1 addition & 0 deletions src/Phpro/SoapClient/CodeGenerator/Util/Normalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Normalizer
'callable' => 'callable',
'iterable' => 'iterable',
'array' => 'array',
'void' => 'mixed',
];

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Phpro\SoapClient\CodeGenerator\ClassMapGenerator;
use Phpro\SoapClient\CodeGenerator\Model\TypeMap;
use Phpro\SoapClient\Console\Helper\ConfigHelper;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypesManipulatorChain;
use Phpro\SoapClient\Util\Filesystem;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -72,9 +73,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io = new SymfonyStyle($input, $output);

$config = $this->getConfigHelper()->load($input);
// For class-maps, we don't want to do anything with duplicate types:
// All types should be listed with namespace and name, even if that means there will be a duplicate entry.
$config->setDuplicateTypeIntersectStrategy(new TypesManipulatorChain());

$typeMap = TypeMap::fromMetadata(
non_empty_string()->assert($config->getTypeNamespace()),
$config->getEngine()->getMetadata()->getTypes()
$config->getManipulatedMetadata()->getTypes(),
);

$generator = new ClassMapGenerator(
Expand All @@ -86,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->handleClassmap($generator, $typeMap, $path);

$io->success('Generated classmap at ' . $path);

return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
use Phpro\SoapClient\CodeGenerator\GeneratorInterface;
use Phpro\SoapClient\CodeGenerator\Model\Client;
use Phpro\SoapClient\CodeGenerator\Model\ClientMethodMap;
use Phpro\SoapClient\CodeGenerator\TypeGenerator;
use Phpro\SoapClient\Console\Helper\ConfigHelper;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypesManipulatorChain;
use Phpro\SoapClient\Soap\Metadata\MetadataFactory;
use Phpro\SoapClient\Util\Filesystem;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
use Laminas\Code\Generator\FileGenerator;
use function Psl\Type\instance_of;
Expand Down Expand Up @@ -77,7 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$destination = $config->getClientDestination().'/'.$config->getClientName().'.php';
$methodMap = ClientMethodMap::fromMetadata(
non_empty_string()->assert($config->getTypeNamespace()),
$config->getEngine()->getMetadata()->getMethods()
$config->getManipulatedMetadata()->getMethods(),
);

$client = new Client(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$config = $this->getConfigHelper()->load($input);
$typeMap = TypeMap::fromMetadata(
non_empty_string()->assert($config->getTypeNamespace()),
MetadataFactory::manipulated(
$config->getEngine()->getMetadata(),
$config->getTypeMetadataOptions()
)->getTypes()
$config->getManipulatedMetadata()->getTypes(),
);
$generator = new TypeGenerator($config->getRuleSet());

Expand Down
Loading

0 comments on commit ae2a668

Please sign in to comment.