Closed faithfulman18 closed 5 years ago
The native PHP SoapClient class is responsible for this behaviour. The SoapClient class often does not support the ref
attribute.
This probably means you have to override the SoapClient in order to update the XML request before it is actually sent.
So, are you saying this would need to happen after you create all the wsdl classes, during a soap request, but right before the actual request is sent out? And if so, are you saying I'd have to edit one of the objects that this classes from this project produces or are you saying that to send the request properly, I cannot use this tool?
You have to do as in the https://github.com/WsdlToPhp/PackageEws365 project by using your proper SoapClient implementation and using it by defining it using the soapclient option
So, just to clarify, we wouldn't actually use the PackageEws365 project itself. You're just using that as an example, right?
And so, are you saying we'd need to add our own SoapClientBase file, and then add the following parameters when running the packageGenerator tool? Basically, pointing specifically to the different soapClient and listing the prefixes. And what if we have one web services with multiple prefixes? Would we do this:
c:\php\php.exe ./wsdltophp.phar generate:package \
--urlorpath="http://domain/webCompany/service?wsdl" \
--destination="C:\wsdlsfolder" \
--composer-name="myproject/mypackage" \
--namespace="service" \
--composer-settings="config.disable-tls:true" \
--validation=false \
--**soapclient**="\SoapClient\SoapClientBase" \
--**prefix**="common"\
--**prefix**="virtual" \
--force;
Is that about what you mean?
The SoapClient overridding has to be used to override the SOAP XML request before it is actually sent as it has been done in https://github.com/WsdlToPhp/PackageColissimoPostage/blob/develop/SoapClient/SoapClient.php. PackageEws365 and this last project are samples indeed. Is it more clear?
In addition, the prefix option you're refering are not fit for this. These are used to prefix the class names, which is an old option which is rarely used nowadays thanks to namespaces.
Okay, I think I follow. I assumed that because one of the links above you sent showed the prefix option being used. So, if I understand, it's really not going to change how we run the WsdlToPhp PackageGenerator process, with the exception of the custom SoapClient I would need to create, is that about right?
So, the process in creating the wsdl class files would be something like this:
c:\php\php.exe ./wsdltophp.phar generate:package \
--urlorpath="http://domain/webCompany/service?wsdl" \
--destination="C:\wsdlsfolder" \
--composer-name="myproject/mypackage" \
--namespace="service" \
--composer-settings="config.disable-tls:true" \
--validation=false \
**--soapclient="\SoapClient\SoapClientBase" \**
--force;
And then through doing some custom work, I'd then have to modify any field that needs a prefix using the custom soapClient. Is that correct?
So, if I understand, it's really not going to change how we run the WsdlToPhp PackageGenerator process, with the exception of the custom SoapClient I would need to create, is that about right?
YES
And then through doing some custom work, I'd then have to modify any field that needs a prefix using the custom soapClient. Is that correct?
YES
By the way --composer-settings="config.disable-tls:true"
is also a sample that you should normally not use.
Yes, I know that it isn't something we normally want to use (disable-tls). I think I had that in there because at some point Windows didn't trust the composer sites SSL certificate .... either that or we had a vendor who was using a self signed cert for their API.
So, basically, since the default soapClient is making the attribute just show as:
<Request MessageId="1234564789">
and I need to be more like:
<Request common:MessageId="1234564789">
my custom soapClient file would need to be something like this:
<?php
namespace SoapClient;
class SoapClient extends \SoapClient
{
public $lastRequest;
public function __doRequest($request, $location, $action, $version, $oneWay = null)
{
$request = str_replace(' xsi:type="ns1:MessageId"', 'ns1:common:MessageId', $request);
$response = parent::__doRequest($this->lastRequest = $request, $location, $action, $version, $oneWay);
$response = substr($response, strpos($response, '<soap:Envelope '), strrpos($response, '</soap:Envelope>') - strpos($response, '<soap:Envelope ') + strlen('</soap:Envelope>'));
return '<?xml version="1.0" encoding="UTF-8"?>' . trim($response);
}
public function __getLastRequest()
{
return $this->lastRequest;
}
}
Does that sound about right?
Sort of yes, but the sample I gave you is particular as the WS responds with a boundary multipart content, this is why it is trimmed from the final XML request response and you should not have to do so. In addition, try and see who it goes. If the namespace common
is not declared at the root level, it will certainly throw an error so you would have to edit the XML with a DOMDocument in order to properly set the namespace URI whic would be added to the root level tag. In addition, you probably don't need the ns1 prefix, it's up to you
So, I think i'm a bit confused again.
How would I know if "the namespace 'common' is not declared at the root level" ?
Where do I even look for that? Is that something I would find in the WSDL itself? or the XSD schema?
It must be declared in the XML request. So either it is already declared at the root level or you have to add it.
So, the first line in the XML request looks about like this:
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
**xmlns:common="http://www.site.com/common/dto/common"**
xmlns:thisServicedto="http://www.site.com/thisService/dto"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://www.site.com/thisService"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="ThisService"
targetNamespace="http://www.site.com/thisService">
Also, where in my generated files would I verify that my custom soapClient files took properly? Since I am adding this additional flag to the generation command:
--soapclient="\SoapClient\SoapClient"
Where there is a SoapClient folder with a SoapClient.php file inside of it, and the SoapClient folder lives at the same root folder that the PHAR file exists in.
So, the first line in the XML request looks about like this:
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" **xmlns:common="http://www.site.com/common/dto/common"** xmlns:thisServicedto="http://www.site.com/thisService/dto" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.site.com/thisService" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ThisService" targetNamespace="http://www.site.com/thisService">
This is ok then, you just have to prefix the attribute.
Also, where in my generated files would I verify that my custom soapClient files took properly? Since I am adding this additional flag to the generation command:
--soapclient="\SoapClient\SoapClient"
Where there is a SoapClient folder with a SoapClient.php file inside of it, and the SoapClient folder lives at the same root folder that the PHAR file exists in.
This is up to you. You can place your SoapClient wherever your want under any namespace you want. Then use the full namespaced name in the command line argument. Then you can check that it is well taken into account by looking to the generated classed under the ServiceType folder. They must extends your SoapClient class passed as the argument value. Is this you were asking?
Sort of. So, just to make sure I'm following. The --soapclient='folder/file' doesn't actually need to exist at the time the files are being generated from the WSDL. This is just telling the packageGenerator where the folder and file are to be used as the 'soapClient' at the time a soap call is being set out and uses that version rather than the base package included with packageGenerator. Is that correct?
And so, I noticed in the few examples you gave me the folder has two files in it. Inside the folder 'SoapClient' there exist: SoapClient.php and SoapClientBase.php
Do these both need to be here or just one or the other?
This is not he folder, this is the fully-qualified class name, that indeed does not need to exist at the time of the generation. The two files need to exist:
Oh, ok.
So where do I put the files once and where do I reference them for inclusion when I need to make a soap call.
?
Please look carefully to the PackageEws project:
autoload
and require
sections, the SoapClient fully-qualified name must match its location, then composer dumpautoload
commandOkay I am not understanding.
So, I thought you previously said I didn't need to have the soap client files present when run to the phpGenerator, but now it sounds as if you are saying you do need to. So I'm back to my original question.
Once you have run the generator, where do I look inside my generated output files to make sure that the outputted classes took the changes for my custom soap client changes?
I've downloaded and searched through both the PackageColissimoPostage and PackageEws365 projects code and I don't see anywhere that there is an include or a require statement to include the SoapClient/SoapClient.php or SoapClient/SoapClientBase.php within either project.
I feel like a step in the instructions is being left out on how to make this work.
What am I missing?
Since I cannot seem to figure what I'm supposed to do for sure, I've tried to create the wsdls with the --soapclient parameter and manually create a copy of the soapClient.php and soapClientBase.php files in my own code base and require the soapClient on script load.
When I do that i seem to get this error:
Fatal error: SoapClient::SoapClient(): $wsdl must be string or null in C:\testsite\testService.php on line 36 Call Stack: 0.0014 42360216 1. {main}() C:\testsite\testService.php:0 0.0077 42550664 2. testService\ServiceType\Get->SoapClient() C:\testsite\testService.php:36
Here is my code basically:
<?php
include "testClientFiles/testService/SoapClient/SoapClient.php";
//load the library
require_once 'testClientFiles/testService/vendor/autoload.php';
//set the type of command needed (get and create)
$options = array(
\WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_URL => 'http://testsite/testService/testService?wsdl',
\WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_CLASSMAP => \testService\ClassMap::get(),
);
$get = new \testService\ServiceType\Get($options);
$getInfoRequest = new \testService\StructType\GetTestServiceRequest();
$getInfoRequest->MessageId = '00123456789';
$getInfo = new \testService\StructType\GetTestService($getInfoRequest);
if ($get->getTestService($getInfo) === false) {
print_r($get->getLastRequest());
}else{
print_r ($get->getResult());
}
?>
To clarify the generation process, when you use:
php wsdltophp.phar generate:package \
--urlorpath="https://ws.colissimo.fr/sls-ws/SlsServiceWS?wsdl" \
--destination=$DEST \
--composer-name="wsdltophp/package-colissimo-postage" \
--composer-settings="autoload.psr-4.SoapClient\:./SoapClient/" \
--addcomments="author:WsdlToPhp <contact@wsdltophp.com>" \
--soapclient="\SoapClient\SoapClientBase" \
--namespace="ColissimoPostage" \
--force;
This means:
composer install
is called. This allows to have an autoloader up to date right after the generation. autoload.psr-4.SoapClient\:./SoapClient/
allows to add namespace mapping to the autoload.For recall, this is composer that is responsible for autoloading the classes. This is why there is only one require_once 'vendor/autoload.php'
. This file is generated by composer and takes care of autoloading the class you use when it is needed.
One advice, I would not recommend to directly use the public properties such as
$getInfoRequest = new \testService\StructType\GetTestServiceRequest();
$getInfoRequest->MessageId = '00123456789';
but
$getInfoRequest = new \testService\StructType\GetTestServiceRequest();
$getInfoRequest->setMessageId('00123456789');
As it allows to pass through the validation rules if there is any and to be sure of the data you're sending before actually sending them as it would throw an exception if the data does not match the WSDL requirements.
I think I can't be clearer from now :wink:
Ok. So, any idea why the above error is happening?
The error seems to have to do with this:
$get = new \testService\ServiceType\Get($options);
Check your SoapClientBase and SoapClient classes as I presume you might inverted the two classes or something like this. Can you post your classes/project on a Github project or on Gist so I can have a better idea?
I've generated a project to an opensource API trying to make use of the SoapClient feature here:
https://github.com/faithfulman18/PackageGeneratorSoap/tree/master/testProject
You will notice I have added the SoapClient folder in that root folder and was trying to include the SoapClient.php file in my script doing the connection to the webservice. Here is my test script that produces this error:
<?php
include "testProject/SoapClient/SoapClient.php";
//load the library
require_once 'testProject/vendor/autoload.php';
//set the type of command needed (get and create)
$options = array(
\WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_URL => 'http://soapclient.com/xml/SQLDataSoap.WSDL',
\WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_CLASSMAP => \TestSite\ClassMap::get(),
);
$process = new \TestSite\ServiceType\Process($options);
$SRLFile = "test";
$RequestName = "test";
$key = "test";
if ($process->ProcessSRL($SRLFile, $RequestName, $key) !== false) {
print_r($process->getResult());
} else {
print_r($process->getLastError());
}
?>
I created 2 pull requests to show you what should be changed
Okay, that did the trick. So, now I'm back to my original problem where the MessageId needs to be prefixed and isn't. I went to the SoapClient.php and added this line in the __doRequest function:
$request = str_replace(' MessageId=', ' common:MessageId=', $request);
I also tried this since SOAPUI does it this way:
$request = str_replace(' MessageId=', ' com:MessageId=', $request);
Both result in this:
Warning: DOMDocument::loadXML(): Namespace prefix com for MessageId on Request is not defined in Entity, line: 2 in C:\testSite\SoapClient\SoapClient.php on line 57
As a reminder, the WSDL looks like this:
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:common="http://www.site.com/common/dto/common"
xmlns:thisServicedto="http://www.site.com/thisService/dto"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://www.site.com/thisService"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="ThisService"
targetNamespace="http://www.site.com/thisService">
Any ideas?
Is http://soapclient.com/xml/SQLDataSoap.WSDL really the WS you're calling or another one? I must admit I would need the WSDL to test by my own to be able to give you an accurate answer. Can you send it to me at contact@mikael-delsol.fr?
No, that was just an example to get the alternate SoapClient working. Thanks again on that.
As far as this last remaining issue, we are bound by a NDA (Non Disclosure Agreement) and cannot provide that, besides that, it isn't a public web service but one that runs internal to an organization.
Is there something more I should be looking for that might help? Is there a way to print out what NameSpace's are being detected since that seems to be something to do with the error and it appears that these are in the root of the WSDL like you asked earlier?
Well, I've figured out why this last change isn't working, though I'm not sure what the fix is or why this tool isn't doing this correctly. I copy'd out the last soap request and the only thing that is truly different from what SOAPUI generates and what this PHP project is generating is the Soap Envelope, see below:
SOAP UI generates this:
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://www.testsite.com/testService"
xmlns:com="http://www.testsite.com/common/dto/common">
PHP with this tool is generating this:
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://www.testsite.com/testService">
If I paste in the full request from what this PHP project is trying to send and copy/paste in that last part of the soap envelope xmlns:com="http://www.testsite.com/common/dto/common"
and run the command in SOAPUI, it works properly. So, if we can fix the issue with the envelope, I think it will be working.
Any ideas?
Well, I've figured out why this last change isn't working, though I'm not sure what the fix is or why this tool isn't doing this correctly. I copy'd out the last soap request and the only thing that is truly different from what SOAPUI does and what this PHP project is doing is the Soap Envelope, see below:
SOAP UI generates this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.testsite.com/testService" xmlns:com="http://www.testsite.com/common/dto/common">
PHP with this tool is generating this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.testsite.com/testService">
Any ideas?
Indeed, i was just going to reply...
So, the first line in the XML request looks about like this:
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" **xmlns:common="http://www.site.com/common/dto/common"** xmlns:thisServicedto="http://www.site.com/thisService/dto" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.site.com/thisService" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ThisService" targetNamespace="http://www.site.com/thisService">
This is ok then, you just have to prefix the attribute.
This is not OK after looking back again. This is the WSDL definition that contains the common namespace, not the XML request. This is why I indicated at https://github.com/WsdlToPhp/PackageGenerator/issues/163#issuecomment-453413728 that you need to add the namespace to the XML request, this was certainly not clear at the time for you (and not a clear explanation I must admit). So now you know what to do, add the namespace to the XML request :wink:, the way you want and think is best.
Okay, so I got a successful response for this first method call and in the SoapClient.php my change now looks like this:
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$request = str_replace(' MessageId=', ' com:MessageId=', $request);
$request = str_replace('><SOAP-ENV:Body>', ' xmlns:com="http://www.site.com/common/dto/common"><SOAP-ENV:Body>', $request);
$this->removeEmptyTags($request);
return parent::__doRequest($this->__getLastRequest(), $location, $action, $version, $one_way);
}
It works, ... it's kind of messy, but it functions properly.
So, my next question is, why doesn't the tool read in the additional name spaces? Is that a deficiency in the current tool?
The issue comes from the native PHP SoapClient class, the generated code does not take care of the XML request, it mainly provides a way to easily construct the request and handle the response all with an OOP approach.
The native PHP SoapClient class works well in pretty most of the cases, except the exceptions as yours :wink: (and some others). So I don't pretend to replace the native SoapClient class.
Maybe these links can help understand:
Yeah, I get that it's a deficiency in PHP's SoapClient.
As far as my __doRequest function above, is that the best way to go about it, or would there be some other cleaner way? Also, since you are making classes based off the WSDL, couldn't you make a class or variable of some sort that could hold on to the start of what the SoapClient Envelope should be and a function to swap it out correctly?
As far as my __doRequest function above, is that the best way to go about it, or would there be some other cleaner way?
You'd have to load the XML with the DOMDocument class, then update the MessageId attribute in order to add the namespace to it that should automatically be added either to the root node or itself. More complex, depends on your skills.
Also, since you are making classes based off the WSDL, couldn't you make a class or variable of some sort that could hold on to the start of what the SoapClient Envelope should be and a function to swap it out correctly?
I don't think I can predict such deficiency on the generation, so hard to foresse a workaround.
Okay.
So, the stack overflow article you pointed out earlier, has a response saying:
by adding elementFormDefault="qualified"
to the XSD schema it fixed the missing namespace. Is this something that I should be asking the vendor to add to their XSD ? Or am I misreading that?
This is on the bottom response of this:
https://stackoverflow.com/questions/47326729/php-soapclient-does-not-add-namespace-to-all-elements
by adding elementFormDefault="qualified" to the XSD schema it fixed the missing namespace. Is this something that I should be asking the vendor to add to their XSD ?
Indeed, it's free to ask, it would be a good thing to be sure of the real effect of this attribute on the SoapClient class when it's generating the XML request.
Since I cannot actually change their XSD files, is there a way for me to really do that?
But if the provider changes the WSDL, it can break your scripts
Nothing changed .... 👎
I'm wondering if because it's still using WSDL mode if it is actually looking at the WSDL online still.
I remember another issue that the vendor had caused and you were going to work on adding the WSDL_URI option so we could bypass the WSDL mode however I don't seem to be able to get that to work where I need to set the WSDL_URL to null and it still doesn't like that even though I'm setting the WSDL_URI.
I take it back, I realized I didn't set the WSDL_URI and WSDL_LOCATION and now that I do that it looks very different. The entire request does, but just to focus on the enveloper for a second it is now doing this:
WSDL Mode
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://www.testsite.com/testService">
Non-WSDL Mode
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.site.com/testService/dto"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
The Non-WSDL Mode puts everything in a weird format actually ... and this didn't work either.
I finally tried copying those same modified .wsdl and both .xsd files out to a web server and then tried to do the request again. What is odd is adding the elementFormDefault="qualified" on one of the two .xsd files schema tags, did indeed the one additional header in the SoapClient SoapEnvelope, however it didn't work on the second .xsd file, but only the first one. So, not "common.xsd" but the .xsd that matched the service of the name.
Another oddity, is that after I added that one, it added some references to some fields, that when trying the command in SoapUI and manually fixing the MessageId and manually adding the 3rd header, it was happy with the MessageId, but not the fields that the added references were placed on.
I wish that PHP would fix this issue. So frustrating.
I wish that PHP would fix this issue. So frustrating.
I sympathize.
Can we now consider this issue closed, at least that this project can't do nothing about it? Thx for your feedbacks by the way
Almost.
I am trying to understand the parameters for the custom SoapClient part. Where I had been doing
--soapclient="\SoapClient\SoapClient"
And earlier when I mentioned I was doing that you didn't tell me it was wrong, and I could have sworn I had seen that somewhere on the site.
So, it apparently needed to be this:
--soapclient="\SoapClient\SoapClientBase"
So, the above is just the class name? Is that correct?
And somehow I didn't even notice this line originally:
--composer-settings="autoload.psr-4.SoapClient\:./SoapClient/"
Are these both just referencing a class name or is one actually referencing a folder path?
I'm trying to understand and also if I decided I wanted to place my SoapClient folder in another folder outside the main generated WSDLs, wondering if that is even possible.
Thanks,
Kevin
I can't see everything, moreover you can name and place things as you want where you want, it's up to you. I'm just blind, I only see what you write. It seems to me you're not familiar with composer, so please read back my previous comment.
I'm sorry for the misunderstanding, and you are right that I'm not very familiar with working with composer directly. I do understand that you are autoloading classes through the autoloader. Maybe I can ask it in a different way and you can let me know if this is possible.
If I want to have my wsdlgeneration dump into this folder:
c:\myProjects\ThisProject}
and I wanted to store my soapClient in a completely separate folder, for example:
c:\misc\manaualSoapClients\SoapClient.php and SoapClientBase.php
Is that possible, or is it just the nature of the namespace and autoloading that the SoapClient has to live somewhere within the root of the folder the autoloader lives? (I hope that helps make sense of my question)
In my point of view there is no point of doing this as it should be versionned with the generated PHP SDK.
Is that possible, or is it just the nature of the namespace and autoloading that the SoapClient has to live somewhere within the root of the folder the autoloader lives? (I hope that helps make sense of my question)
Why don't you test and see by yourself? Did you search for that at least? This is a place for issues about the project not for general questions.
Pardon my annoyance but I'm closing this issue now.
I apologize for annoying. It wasn't my intention. I do appreciate all of your help on these past few issues. So, thank you again.
Have you run into a situation where a WSDL needs to have a prefix and the current tool doesn't allow for that to work properly? It appears this tool is stripping the prefix off of the attribute when it's trying to send the request, which is getting me a message like this:
Validation failed: cvc-complex-type.3.2.2: Attribute 'MessageId' is not allowed to appear in element 'Request'.
The WSDL is expecting it to be common:MessageId, but the tool is generating the code in such a way that that doesn't seem to happen. Here is the field in the XML schema for the WSDL:
And this tool seems to be creating the class files as this:
and in the soap request it similarly set this the field as this:
Because it isn't what the web service is expecting, it fails with the before mentioned error.
Any ideas?
Thanks,
Kevin