Skip to content

Commit

Permalink
Introduce a way to replace types during code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Jun 11, 2024
1 parent 3f7dc6a commit cc930b7
Show file tree
Hide file tree
Showing 17 changed files with 323 additions and 107 deletions.
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
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
5 changes: 2 additions & 3 deletions src/Phpro/SoapClient/Console/Helper/ConfigHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,15 @@ public function getName():string
/**
* Attempts to load the configuration file, returns it on success
* @param InputInterface $input
* @return ConfigInterface
*/
public function load(InputInterface $input): ConfigInterface
public function load(InputInterface $input): Config
{
$configFile = $input->getOption('config');
if (!$configFile || !$this->filesystem->fileExists($configFile)) {
throw InvalidArgumentException::invalidConfigFile();
}
$config = include $configFile;
if (!$config instanceof ConfigInterface) {
if (!$config instanceof Config) {
throw InvalidArgumentException::invalidConfigFile();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static function destinationConfigurationIsMissing(): self

public static function invalidConfigFile(): self
{
return new static('You have to provide a code-generator config file which returns a ConfigInterface.');
return new static('You have to provide a code-generator config file which returns a Config class instance.');
}

public static function clientNamespaceIsMissing(): self
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

namespace Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer;

use Soap\Engine\Metadata\Model\TypeMeta;
use Soap\Engine\Metadata\Model\XsdType;
use Soap\WsdlReader\Metadata\Detector\ApacheMapDetector;
use Soap\WsdlReader\Metadata\Predicate\IsOfType;

final class ApacheMapReplacer implements TypeReplacer
{
public function __invoke(XsdType $xsdType): XsdType
{
$check = new IsOfType(ApacheMapDetector::NAMESPACE, 'Map');
if (!$check($xsdType)) {
return $xsdType;
}

return $xsdType->copy('array')
->withBaseType('array')
->withMeta(
// Mark as simple to make sure no additional types are generated for this type.
static fn (TypeMeta $meta): TypeMeta => $meta->withIsSimple(true)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);

namespace Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer;

use Phpro\SoapClient\Soap\Metadata\Manipulators\MethodsManipulatorInterface;
use Soap\Engine\Metadata\Collection\MethodCollection;
use Soap\Engine\Metadata\Collection\ParameterCollection;
use Soap\Engine\Metadata\Model\Method;
use Soap\Engine\Metadata\Model\Parameter;
use function Psl\Vec\map;

final class ReplaceMethodTypesManipulator implements MethodsManipulatorInterface
{
public function __construct(
private readonly TypeReplacer $typeReplacer
) {
}

public function __invoke(MethodCollection $methods): MethodCollection
{
return new MethodCollection(
...map($methods, $this->replaceMethodTypes(...))
);
}

private function replaceMethodTypes(Method $method): Method
{
return new Method(
$method->getName(),
new ParameterCollection(...map(
$method->getParameters(),
fn(Parameter $parameter): Parameter => new Parameter(
$parameter->getName(),
($this->typeReplacer)($parameter->getType()),
)
)),
($this->typeReplacer)($method->getReturnType()),
);
}
}
Loading

0 comments on commit cc930b7

Please sign in to comment.