Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using classes that extends another #536

Closed
LazyTechwork opened this issue Aug 2, 2024 · 3 comments
Closed

Using classes that extends another #536

LazyTechwork opened this issue Aug 2, 2024 · 3 comments

Comments

@LazyTechwork
Copy link

LazyTechwork commented Aug 2, 2024

Q A
Version 4.0.0

Hello! I have another problem with types, as in #534. The BaseIdentifier class is extended by Identifier, and the documentation for my WSDL states that I need to explicitly specify the type of the object that is being extended. So, when making a request, I use Identifier, but when passing arguments, method signature using BaseIdentifier. How can I add the xsi:type attribute to the extended class?

Chunk of types in WSDL:

<s:complexType name="BaseIdentifier">
	<s:complexContent mixed="false">
		<s:extension base="tns:BaseObject">
			<s:sequence>
				<s:element minOccurs="0" maxOccurs="1" name="CODE" type="s:string"/>
				<s:element minOccurs="1" maxOccurs="1" name="PERSON_ID" type="s1:guid"/>
				<s:element minOccurs="1" maxOccurs="1" name="IS_PRIMARY" type="s:boolean"/>
			</s:sequence>
		</s:extension>
	</s:complexContent>
</s:complexType>
<s:complexType name="Identifier">
	<s:complexContent mixed="false">
		<s:extension base="tns:BaseIdentifier">
			<s:sequence>
				<s:element minOccurs="1" maxOccurs="1" name="ACCGROUP_ID" type="s1:guid"/>
				<s:element minOccurs="1" maxOccurs="1" name="PRIVILEGE_MASK" type="s:long"/>
				<s:element minOccurs="1" maxOccurs="1" name="IDENTIFTYPE" type="s:int"/>
				<s:element minOccurs="0" maxOccurs="1" name="NAME" type="s:string"/>
			</s:sequence>
		</s:extension>
	</s:complexContent>
</s:complexType>

I tried to do something like this:

->addComplexTypeConverter('http://parsec.ru/Parsec3IntergationService', 'BaseIdentifier', new ComplexContextEnhancer(BaseIdentifier::class))
->addComplexTypeConverter('http://parsec.ru/Parsec3IntergationService', 'Identifier', new ComplexContextEnhancer(Identifier::class))
class ComplexContextEnhancer implements ElementContextEnhancer, XmlEncoder
{
    public function __construct(private readonly string $className) {}

    public function iso(Context $context): Iso
    {
        return (new ObjectEncoder($this->className))->iso($context);
    }

    public function enhanceElementContext(Context $context): Context
    {
        return $context->withBindingUse(BindingUse::ENCODED);
    }
}

But I still get an instance of BaseIdentifier in the request:

<SOAP-ENV:Envelope
	xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
	<SOAP-ENV:Body
		xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
		<tns:AddPersonIdentifier
			xmlns:tns="http://parsec.ru/Parsec3IntergationService">
			<tns:personEditSessionID
				xmlns:tns="http://parsec.ru/Parsec3IntergationService">f6330ad4-4084-4bf7-aa7b-c394c893dba8
			</tns:personEditSessionID>
			<tns:identifier
				xmlns:tns="http://parsec.ru/Parsec3IntergationService">
				<tns:CODE
					xmlns:tns="http://parsec.ru/Parsec3IntergationService">00001351
				</tns:CODE>
				<tns:PERSON_ID
					xmlns:tns="http://parsec.ru/Parsec3IntergationService">6a5476a7-0494-46c3-a405-f832afa25544
				</tns:PERSON_ID>
				<tns:IS_PRIMARY
					xmlns:tns="http://parsec.ru/Parsec3IntergationService">false
				</tns:IS_PRIMARY>
			</tns:identifier>
		</tns:AddPersonIdentifier>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
@LazyTechwork
Copy link
Author

LazyTechwork commented Aug 2, 2024

Translated chunk of documentation of service:

When passed to the input field as the identifier of the structure Identifier or IdentifierTemp must be specified in the appropriate order in the identifier element attribute xsi:type="Identifier" or xsi:type="IdentifierTemp" appropriately.
Example:

<soap:Envelope
	xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
	<soap:Body>
		<AddPersonIdentifier
			xmlns="http://parsec.ru/ParsecIntergationService">
			<personEditSessionID>a0c8c558-0893-4fb6-b2ea528d2f08edd2</personEditSessionID>
			<identifier xsi:type="Identifier">
				<CODE>11111112</CODE>
				<PERSON_ID>be4d3ce8-d830-4796-9197-7fa65c78d13f</PERSON_ID>
				<IS_PRIMARY>false</IS_PRIMARY>
				<ACCGROUP_ID>111111111-2222-3333-4444-555555555555</ACCGROUP_ID>
			</identifier>
		</AddPersonIdentifier>
	</soap:Body>
</soap:Envelope>

@veewee
Copy link
Contributor

veewee commented Aug 2, 2024

In comparison to simple types, complex types are in direct control of the element wrapping it's data.

Maybe this would work for you?

use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\ObjectEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use VeeWee\Reflecta\Iso\Iso;

$registry->addComplexTypeConverter(
    'http://parsec.ru/Parsec3IntergationService',
    'BaseIdentifier',
    new class implements XmlEncoder
    {
        public function iso(Context $context): Iso
        {
            return (new ObjectEncoder(Identifier::class))->iso(
                $context
                    ->withBindingUse(BindingUse::ENCODED)
                    ->withType($context->type->copy('Identifier'))
            );
        }
    }
);

(ℹ️ Note that I hardcoded it to the Identifier type - you might need to make it smarter to detect e.g. IdentifierTemp.)

On data input:

$encoded = $driver->encode('AddPersonIdentifier', [
    [
        'personEditSessionID' => '962bf124-2b58-4ed3-b63f-adaa73a29e91',
        'identifier' => [
            'CODE' => '11111112',
            'PERSON_ID' => 'be4d3ce8-d830-4796-9197-7fa65c78d13f',
            'IS_PRIMARY' => false,
            'ACCGROUP_ID' => '111111111-2222-3333-4444-555555555555',
            'PRIVILEGE_MASK' => 0,
            'IDENTIFTYPE' => 0,
            'NAME' => '',
        ]
    ]
]);

Resulting in this XML:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
        <tns:AddPersonIdentifier xmlns:tns="http://parsec.ru/Parsec3IntergationService">
            <tns:personEditSessionID xmlns:tns="http://parsec.ru/Parsec3IntergationService">
                962bf124-2b58-4ed3-b63f-adaa73a29e91
            </tns:personEditSessionID>
            <tns:identifier xsi:type="tns:Identifier" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                            xmlns:tns="http://parsec.ru/Parsec3IntergationService">
                <tns:CODE xmlns:s="http://www.w3.org/2001/XMLSchema" xsi:type="s:string"
                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                          xmlns:tns="http://parsec.ru/Parsec3IntergationService">11111112
                </tns:CODE>
                <tns:PERSON_ID xmlns:s1="http://microsoft.com/wsdl/types/" xsi:type="s1:guid"
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                               xmlns:tns="http://parsec.ru/Parsec3IntergationService">
                    be4d3ce8-d830-4796-9197-7fa65c78d13f
                </tns:PERSON_ID>
                <tns:IS_PRIMARY xmlns:s="http://www.w3.org/2001/XMLSchema" xsi:type="s:boolean"
                                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                xmlns:tns="http://parsec.ru/Parsec3IntergationService">false
                </tns:IS_PRIMARY>
                <tns:ACCGROUP_ID xmlns:s1="http://microsoft.com/wsdl/types/" xsi:type="s1:guid"
                                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                 xmlns:tns="http://parsec.ru/Parsec3IntergationService">
                    111111111-2222-3333-4444-555555555555
                </tns:ACCGROUP_ID>
                <tns:PRIVILEGE_MASK xmlns:s="http://www.w3.org/2001/XMLSchema" xsi:type="s:long"
                                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                    xmlns:tns="http://parsec.ru/Parsec3IntergationService">0
                </tns:PRIVILEGE_MASK>
                <tns:IDENTIFTYPE xmlns:s="http://www.w3.org/2001/XMLSchema" xsi:type="s:int"
                                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                 xmlns:tns="http://parsec.ru/Parsec3IntergationService">0
                </tns:IDENTIFTYPE>
                <tns:NAME xmlns:s="http://www.w3.org/2001/XMLSchema" xsi:type="s:string"
                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                          xmlns:tns="http://parsec.ru/Parsec3IntergationService"></tns:NAME>
            </tns:identifier>
        </tns:AddPersonIdentifier>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

(ℹ️ Note that it also adds xsi:type on all it's children. Normally, that shouldn't be a big problem since they are the same as in the WSDL)

@LazyTechwork
Copy link
Author

Well, I have come up with a more intelligent solution based on what you suggested.

ParsecInvertedClassmap for mapping PHP classes to XSD Types:

use Soap\Engine\Metadata\Model\XsdType;

class ParsecInvertedClassmap
{
    /**
     * @var array<string, XsdType>|null
     */
    private static ?array $collection = null;

    /**
     * @return array<string, XsdType>
     */
    public static function getCollection(): array
    {
        if (self::$collection === null) {
            self::$collection = [];
            foreach (ParsecClassmap::getCollection() as $classmap) {
                self::$collection[$classmap->getPhpClassName()] = XsdType::create($classmap->getXmlType())
                    ->withXmlNamespace($classmap->getXmlNamespace())
                    ->withXmlTypeName($classmap->getXmlType());
            }
        }

        return self::$collection;
    }
}

ComplexContextEnhancer:

use LazyTechwork\Parsec\ParsecInvertedClassmap;
use Soap\Encoding\Encoder\Context;
use Soap\Encoding\Encoder\ObjectEncoder;
use Soap\Encoding\Encoder\XmlEncoder;
use Soap\Encoding\Xml\Node\Element;
use Soap\WsdlReader\Model\Definitions\BindingUse;
use VeeWee\Reflecta\Iso\Iso;

class ComplexContextEnhancer implements XmlEncoder
{
    public function __construct(private readonly string $baseClass)
    {
    }

    public function iso(Context $context): Iso
    {
        return new Iso(
            fn (object|array $value): string => $this->getEncoder($context, $value)->to($value),
            fn (string|Element $value): object => (new ObjectEncoder($this->baseClass))->iso($context)->from($value)
        );
    }

    private function getEncoder(Context $context, mixed $value): Iso
    {
        if (is_a($value, $this->baseClass)) {
            return (new ObjectEncoder($this->baseClass))->iso($context
                ->withBindingUse(BindingUse::ENCODED)
                ->withType($context->type->copy(ParsecInvertedClassmap::getCollection()[get_class($value)]->getName())));
        }

        return (new ObjectEncoder($this->baseClass))->iso($context);
    }
}

And complex type converter in Client Factory:

use LazyTechwork\Parsec\Types\BaseIdentifier;
use LazyTechwork\Parsec\Encoders\ComplexContextEnhancer;

->addComplexTypeConverter(
    'http://parsec.ru/Parsec3IntergationService',
    'BaseIdentifier',
    new ComplexContextEnhancer(BaseIdentifier::class)
)

@veewee Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants