Skip to content

Commit

Permalink
Merge pull request #548 from veewee/use-external-classes-as-prop-types
Browse files Browse the repository at this point in the history
Use external classes as property types.
  • Loading branch information
veewee authored Sep 27, 2024
2 parents a23f11d + dfa7fde commit 8946e9c
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 6 deletions.
31 changes: 30 additions & 1 deletion docs/drivers/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ return Config::create()

### Type replacements

Depending on what XML encoders you configure, you might want to replace some types with other types.
Depending on what [SOAP encoders](https://github.com/php-soap/encoding#encoder) 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.
Expand Down Expand Up @@ -99,3 +99,32 @@ The TypeReplacers contain a default set of type replacements that are being used
* `array` for SOAP 1.1 and 1.2 Arrays
* `object` for SOAP 1.1 Objects
* `array` for Apache Map types


#### Using an existing 3rd party class

If you want to replace a type with an existing 3rd party class, you can copy the type with the fully qualified class name of the 3rd party class.
That way, the property type of the generated code will be set to the newly linked class.
This type replacer is only being used for generating code.
You will still need to implement the logic to convert the type to the 3rd party class in a [custom SOAP encoder](https://github.com/php-soap/encoding#encoder).

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

class GuidTypeReplacer implements TypeReplacer
{
public function __invoke(XsdType $xsdType): XsdType
{
$check = new IsOfType('http://microsoft.com/wsdl/types/', 'guid');
if (!$check($xsdType)) {
return $xsdType;
}

return $xsdType->copy(\Ramsey\Uuid\UuidInterface::class);
}
}
```

**Note**: If you want to use a built-in PHP class, you will need to prefix it with a backslash. For example: `\DateInterval`.
17 changes: 17 additions & 0 deletions spec/Phpro/SoapClient/CodeGenerator/Util/NormalizerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Phpro\SoapClient\CodeGenerator\Util\Normalizer;
use PhpSpec\ObjectBehavior;
use Psl\Option\Option;

/**
* Class NormalizerSpec
Expand Down Expand Up @@ -98,11 +99,27 @@ function it_gets_classname_from_fqn()
$this->getClassNameFromFQN('Vendor\Namespace\MyClass')->shouldReturn('MyClass');
}

function it_gets_namespace_from_fqn()
{
$this->getNamespaceFromFQN('\Namespace\MyClass')->shouldReturn('Namespace');
$this->getNamespaceFromFQN('Vendor\Namespace\MyClass')->shouldReturn('Vendor\Namespace');
$this->getNamespaceFromFQN('ExistingPHPClass')->shouldReturn('');
}

function it_gets_complete_use_statement()
{
$this->getCompleteUseStatement('Namespace\MyClass',
'ClassAlias')->shouldReturn('Namespace\MyClass as ClassAlias');
$this->getCompleteUseStatement('Namespace\MyClass', null)->shouldReturn('Namespace\MyClass');
$this->getCompleteUseStatement('MyClass', '')->shouldReturn('MyClass');
}

/** @test */
public function it_knows_about_third_party_classes(): void
{
$this->isConsideredExistingThirdPartyClass('DateTime')->shouldReturn(false);
$this->isConsideredExistingThirdPartyClass('\DateTime')->shouldReturn(true);
$this->isConsideredExistingThirdPartyClass(Option::class)->shouldReturn(true);
$this->isConsideredExistingThirdPartyClass('Unkown\Class')->shouldReturn(false);
}
}
21 changes: 17 additions & 4 deletions src/Phpro/SoapClient/CodeGenerator/Model/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Property
private $type;

/**
* @var non-empty-string
* @var string
*/
private $namespace;

Expand All @@ -45,7 +45,7 @@ class Property
*
* @param non-empty-string $name
* @param non-empty-string $type
* @param non-empty-string $namespace
* @param string $namespace
*/
public function __construct(string $name, string $type, string $namespace, XsdType $xsdType)
{
Expand All @@ -63,12 +63,21 @@ public function __construct(string $name, string $type, string $namespace, XsdTy
public static function fromMetaData(string $namespace, MetadataProperty $property)
{
$type = $property->getType();
$typeName = $type->getName();

// This makes it possible to set FQCN as type names in the metadata through TypeReplacers.
if (Normalizer::isConsideredExistingThirdPartyClass($typeName)) {
$className = Normalizer::getClassNameFromFQN($typeName);
$type = $type->copy($className)->withBaseType($className);
$namespace = Normalizer::getNamespaceFromFQN($typeName);
}

$meta = $type->getMeta();
$typeName = (new TypeNameCalculator())($type);
$calculatedTypeName = (new TypeNameCalculator())($type);

return new self(
non_empty_string()->assert($property->getName()),
non_empty_string()->assert($typeName),
non_empty_string()->assert($calculatedTypeName),
$namespace,
$type
);
Expand All @@ -91,6 +100,10 @@ public function getType(): string
return $this->type;
}

if (!$this->namespace) {
return '\\'.Normalizer::normalizeClassname($this->type);
}

return '\\'.$this->namespace.'\\'.Normalizer::normalizeClassname($this->type);
}

Expand Down
16 changes: 16 additions & 0 deletions src/Phpro/SoapClient/CodeGenerator/Util/Normalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,17 @@ public static function getClassNameFromFQN(string $name): string
return non_empty_string()->assert(array_pop($arr));
}

/**
* @param non-empty-string $name
*/
public static function getNamespaceFromFQN($name): string
{
$arr = explode('\\', ltrim($name, '\\'));
array_pop($arr);

return implode('\\', $arr);
}

/**
* @param non-empty-string $useName
* @param non-empty-string|null $useAlias
Expand All @@ -266,4 +277,9 @@ public static function getCompleteUseStatement(string $useName, string $useAlias

return $use;
}

public static function isConsideredExistingThirdPartyClass(string $class)
{
return str_contains($class, '\\') && class_exists($class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

use Phpro\SoapClient\CodeGenerator\Model\Property;
use PHPUnit\Framework\TestCase;
use Soap\Engine\Metadata\Model\TypeMeta;
use Psl\Option\Option;
use Soap\Engine\Metadata\Model\Property as EngineProperty;
use Soap\Engine\Metadata\Model\XsdType;

/**
Expand All @@ -23,4 +24,38 @@ public function it_returns_mixed_type_post_php8(): void
self::assertEquals('mixed', $property->getPhpType());
self::assertEquals('mixed', $property->getType());
}

/** @test */
public function it_can_use_fqcn_to_3rd_party_classes_as_type_name(): void
{
$property = Property::fromMetaData(
'MyApp',
new EngineProperty(
'property',
$xsdType = XsdType::create(Option::class)
)
);

self::assertEquals('\\' . Option::class, $property->getType());
self::assertNotSame($xsdType, $property->getXsdType());
self::assertSame('Option', $property->getXsdType()->getName());
self::assertSame('Option', $property->getXsdType()->getBaseType());
}

/** @test */
public function it_can_use_a_php_built_in_class_as_type_name(): void
{
$property = Property::fromMetaData(
'MyApp',
new EngineProperty(
'property',
$xsdType = XsdType::create('\\'. \DateInterval::class)
)
);

self::assertEquals('\\' . \DateInterval::class, $property->getType());
self::assertNotSame($xsdType, $property->getXsdType());
self::assertSame('DateInterval', $property->getXsdType()->getName());
self::assertSame('DateInterval', $property->getXsdType()->getBaseType());
}
}

0 comments on commit 8946e9c

Please sign in to comment.