We're currently looking into doing some performance tweaking on a website which relies heavily on a Soap webservice. But ... our servers are located in Belgium and the webservice we connect to is locate in San Francisco so it's a long distance connection to say the least.
Our website is PHP powered, using PHP's built in SoapClient class.
On average a call to the webservice takes 0.7 seconds and we are doing about 3-5 requests per page. All possible request/response caching is already implemented so we are now looking at other ways to improved the connection speed.
This is the code which instantiates the SoapClient, what i'm looking for now is other ways/methods to improve speed on single requestes. Anyone has idea's or suggestions?
private function _createClient()
{
try {
$wsdl = sprintf($this->config->wsUrl.'?wsdl', $this->wsdl);
$client = new SoapClient($wsdl, array(
'soap_version' => SOAP_1_1,
'encoding' => 'utf-8',
'connection_timeout' => 5,
'cache_wsdl' => 1,
'trace' => 1,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
));
$header_tags = array('username' => new SOAPVar($this->config->wsUsername, XSD_STRING, null, null, null, $this->ns),
'password' => new SOAPVar(md5($this->config->wsPassword), XSD_STRING, null, null, null, $this->ns));
$header_body = new SOAPVar($header_tags, SOAP_ENC_OBJECT);
$header = new SOAPHeader($this->ns, 'AuthHeaderElement', $header_body);
$client->__setSoapHeaders($header);
} catch (SoapFault $e){
controller('Error')->error($id.': Webservice connection error '.$e->getCode());
exit;
}
$this->client = $client;
return $this->client;
}
So, the root problem is number of request you have to do. What about creating grouped services ?
If you are in charge of the webservices, you could create specialized webservices which do multiple operations at the same time so your main app can just do one request per page.
If not you can relocate your app server near SF.
If relocating all the server is not possible and you can not create new specialized webservices, you could add a bridge, located near the webservices server. This bridge would provide the specialized webservices and be in charge of calling the atomic webservices. Instead of 0.7s * 5 you'd have 0.7s + 5 * 0.1 for example.
PHP.INI
output_buffering = On
output_handler = ob_gzhandler
zlib.output_compression = Off
Do you know for sure that it is the network latency slowing down each request? 0.7s seems a long round time, as Benoit says. I'd look at doing some benchmarking - you can do this with curl, although I'm not sure how this would work with your soap client.
Something like:
$ch = curl_init('http://path/to/sanfrancisco/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
$info = curl_getinfo($ch);
$info will return an array including elements for total_time, namelookup_time, connect_time, pretransfer_time, starttransfer_time and redirect_time. From these you should be able to work out whether it's the dns, request, the actual soap server or the response that's taking up the time.
One obvious thing that's just occurred to me is are you requesting the SOAP server via a domain or an IP? If you're using a domain, your dns might be slowing things down significantly (although it will be cached at several stages). Check your local dns time (in your soap client or php.ini - not sure) and the TTL of your domain (in your DNS Zone). Set up a static IP for your SanFran server and reference it that way if not already.
Optimize the Servers (not the client!) HTTP response by using caching and HTTP compressing. Check out the tips at yahoo http://developer.yahoo.com/performance/rules.html
1 You can assert your soap server use gzip compression for http content, as well as your site output does. A 0,7s roundup to SF seems a bit long, it's either webservice is long to answer, either there is an important natwork latency.
If you can, give a try to other hosting companies for your belgium server, in France some got a far better connectivity to the US than others.
I experienced to move a website from one host o another and network latency between Paris and New york has almost doubled ! it's uge and my client with a lot of US visitors was unhappy with it.
The solution of relocating web server to SF can be an option, you'll get a far better connectivity between servers, but be careful of latency if your visitors are mainly located in Europe.
2 You can use an opcode cache mecanism, such as xcache or APC. It wil not change the soap latency, but will improve php execution time.
3 Depending if soap request are repetitive, and how long could a content update could be extended, you can give it a real improvement using cache on soap results. I suggest you to use in-memory caching system (Like xcache/memcached or other) because they're ay much faster than file or DB cache system.
From your class, the createclient method isn't the most adapted exemple functionality to be cached, but for any read operation it's just the best way to perf :
private function _createClient()
{
$xcache_key = 'clientcache'
if (!xcache_isset($key)) {
$ttl = 3600; //one hour cache lifetime
$client = $this->_getClient(); ///private method embedding your soap request
xcache_set($xcache_key, $client, $ttl);
return $client;
}
//return result form mem cache
return xcache_get($xcache_key);
}
The example is for xcache extension, but you can use other systems in a very similar manner
4 To go further you can use similar mecanism to cache your php processing results (like template rendering output and other ressource consumming operations). The key to success with this technic is to know exactly wich part is cached and for how long it will stay withous refreshing.
Any chance of using an AJAX interface.. if the requests can be happening in the background, you will not seem to be left waiting for the response.
Related
EDIT: 11.5 seconds for 28 messages
Single requests work fine. This code below takes 11 seconds, measured using postman setting route in API to access.
Am I doing something wrong? I feel as though it shouldn't take 11 seconds even without cache.
$xs = ChatMessage::where('chat_room_id','=',$roomId)
->with('user')
->orderBy('created_at','DESC')
->get();
foreach($xs as $r){
$translate = new TranslateClient([
'key' => 'xxxxxxxxxxxxxxxxxxxxxxx'
]);
$result = $translate->translate($r->message_english, [
'target' =>'es',
'source' => 'en',
]);
$r->message = $result['text'];
}
return $xs;
I think you can easily speed the process by just moving the client out of the for each loop. You are creating a client each time you iterate. That's not optimal. You should be able to reuse the client per translate call. That should speed your translation process. You can find samples of this usage in the official github client project
Here is a pseudo code sample:
client = new TranslateClient()
foreach(message in messages)
result = client.translate(message)
print(result)
Also, how long is your translated text? You should pass the whole text to be translated into a single call (as long as the supported library allows) So you also reduce the calls done to the API.
If you still have issues you can go by using multiple request in parallel as mentioned in the comments.
Some useful links about this:
PHP Documentation Overview
Translate Client
I am connecting to dynamics 365. It used to work perfectly, i curl to get the token then i use it as an authorization header along with php soapclient and it works, i connect i create a client and i can call my methods.
All of a sudden it decided not to work, and where it used to connect as SOAP 1.1 now it enforced SOAP 1.2
After changing from SOAP 1.1 to SOAP 1.2 ( because I got the error of binding mismatch where it said expecting application/soap+xml and text/xml was found ) So I changed versions and that error disappeared and got replaced with ERROR Fetching HTTP Headers.
That error got stuck for the longest time, people suggested to increase timeout but i put it as high as 500 800 5000 all the same.
Then all of a sudden, it started giving me SOAP ERROR Parsing schema element already defined. I did not change my code, i played for awhile with the headers but to no avail, I even removed the authorization header just to see what is going on and that did nothing i kept getting the same error.
SOAP-ERROR: Parsing Schema: element 'http://schemas.datacontract.org/2004/07/Microsoft.Dynamics.Ax.Xpp:XppObjectBase' already defined [string:Exception:private]
everytime I try to connect I get different kind of parsing schema error even though i am not changing anything in my code:
SOAP-ERROR: Parsing Schema: element 'http://schemas.microsoft.com/2003/10/Serialization/:anyType' already defined [string:Exception:private]
and another
SOAP-ERROR: Parsing Schema: element 'http://schemas.datacontract.org/2004/07/Microsoft.Dynamics.AX.KernelInterop:ProxyBase' already defined [string:Exception:private]
and then sometimes it does get through for a second but with fetching http header error again..
so i can not create a client instance anymore now..
where before i was able to create a client instance but i get an error when I call the method of "Error Fetching HTTP Headers"
something is definitely not stable because my errors are not one.
now some stated the wsdl could be faulty, but this is microsoft and the person i am in contact keeps saying he can not doing anything about it.
Help is this a PHP problem or a dynamics problem or wsdl custom made problem .
And how to solve this.
Thank you.
UPDATE
I'm sorry I mentioned earlier it is Dynamics AX , it turns out it is Dynamics 365 D365. I will keep dynamics ax tag in case it helps someone who needs the solutions provided.
UPDATE
Following is the connection code I am using:
function getAuthenticationHeader()
{
//Each variable has the values for our server
//resource
$appResource = urlencode($appADResource);
//clientID
$appClientID = urlencode($appADClientId);
//appSecret
$appSecret = urlencode($appADSecret);
//username
$appUserID = urlencode($appUserID);
// Password
$appUserPassword = urlencode($password);
// Construct the body for the STS request
$authenticationRequestBody = 'resource='.$appResource.'&client_id='.$appClientID.'&client_secret='.$appSecret.'&grant_type=password&username='.$appUserID.'&password='.$appUserPassword.'&scope=openid';
//Using curl to post the information to STS and get back the authentication response
$ch = curl_init();
// set url
$stsUrl = 'https://login.microsoftonline.com/'.$appTenantId.'/oauth2/token';
curl_setopt($ch, CURLOPT_URL, $stsUrl);
// Get the response back as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// Set the parameters for the request
curl_setopt($ch, CURLOPT_POSTFIELDS, $authenticationRequestBody);
// By default, HTTPS does not work with curl.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// read the output from the post request
$output = curl_exec($ch);
// close curl resource to free up system resources
curl_close($ch);
// decode the response from sts using json decoder
$tokenOutput = json_decode($output);
return $tokenOutput->{'token_type'}.' '.$tokenOutput->{'access_token'};
}
try
{
//WSDL Link
$url = "https://urlToOurServer/services/webservice?wsdl";
$authorizationToken = getAuthenticationHeader();
$context = stream_context_create(array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
),
'https' => array(
'curl_verify_ssl_peer' => false,
'curl_verify_ssl_host' => false
),
'http' => array(
'header' =>'Authorization: '.$authorizationToken
)
));
//Create array of Soap Options
$arrOpt = array(
"soap_version" => SOAP_1_2,
"cache_wsdl" => WSDL_CACHE_NONE,
"exceptions" => true,
'trace' => true,
'encoding' => 'UTF-8',
'stream_context' => $context
);
}catch(Exception $e)
{
print_r($e);
}
I also found this in my wsdl
<sp:IssuedToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<sp:RequestSecurityTokenTemplate>
<trust:TokenType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0
</trust:TokenType>
<trust:KeyType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer
</trust:KeyType>
</sp:RequestSecurityTokenTemplate>
<wsp:Policy>
<sp:RequireInternalReference/>
How can I connect to SAML for Token ?
If everything is pretty much the same, but it's not working, the first thing to do is rule out the most basic AX issues. These may not solve your issue but will be a good first step.
now some stated the wsdl could be faulty, but this is microsoft and the person i am in contact keeps saying he can not doing anything about it.
Whomever that person is, you need to confirm they've done the following:
Confirm the environment and specifically the CIL is fully compiled. Do a full AXBuild and a full CIL to be sure during non-business hours and ensure the output is good. It's basically saying "recompile everything".
Refresh the WCF configuration in the client configuration you are using to connect to AX. This client configuration may be a *.axc file or it may just be the active one. Also refresh the business connector WCF. This is separate and may be what you are using to connect to AX. This is what most people are talking about.
Here's a little article that talks about creating a configuration, but I'll discuss below.
An AX client configuration ultimately is a bunch of text. It's either stored in an .axc file or stored in the registry in a few locations. The Business Connector client config may be the one that is getting missed in your scenario.
If you follow the link above and create a new .axc configuration file and ensure you've clicked "Refresh Configuration" before exporting, when you open the file up in Notepad, you'll see wcfconfig and a bunch of XML following it. That XML is what you're trying to get updated. Creating a new AXC here is just an exercise to help you understand what it is. You can delete the file after you're done looking.
Now, you've basically created a specific configuration file, but that doesn't mean anything is using it. If you call AX32.exe it will default to the one that is loaded in that config screen. Using a file is a way to very specifically choose one. Your code is probably using a specific AXC somewhere that needs either replaced or refreshed OR it's using one that's saved in this window:
It is very likely it is using one of the two that are saved in that configuration window. When you refresh in that window, it ultimately saves the WCF XML in the windows registry on the machine that is hosting the client and/or the AOS in subfolders in HKLM\SOFTWARE\Microsoft\Dynamics\6.0\Configuration. The key(s) is wcfconfig paired with wcfconfigversionid, which just stores a GUID to see if it's up-to-date.
When I say two, I mean most people don't even bother to look at the Business Connector AXC. It's what is highlighted in yellow in my image, and you need to specifically choose and refresh it. This could be important for you. In my image, I do not have it chosen. You need to drop the menu down and choose it.
On a dev machine, you can just clear both of those keys and refresh and you should see whatever configuration you're working on update.
This is a long post, but it's important to rule this part out first. If you have someone who's reasonably experienced administering AX they should know how to ensure these are refreshed.
Since you're saying this is not Dynamics AX, but one of the Dynamics 365 versions. The AX version used to be called Dynamics 365 for Finance and Operations Enterprise Edition but they've changed the licensing/naming again, so I don't even know what it's technically called. Most people call it Dynamics 365 for Operations or some variant.
Either way, you should test the service following the below method. We would need to see more information about the service details and call, so following the below is most likely best.
https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/third-party-service-test
This actually follows on from a previous question I had that, unfortunately, did not receive any answers so I'm not exactly holding my breath for a response but I understand this can be a bit of a tricky issue to solve.
I am currently trying to implement rate limiting on outgoing requests to an external API to match the limit on their end. I have tried to implement a token bucket library (https://github.com/bandwidth-throttle/token-bucket) into the class we are using to manage Guzzle requests for this particular API.
Initially, this seemed to be working as intended but we have now started seeing 429 responses from the API as it no longer seems to be correctly rate limiting the requests.
I have a feeling what is happening is that the number of tokens in the bucket is now being reset every time the API is called due to how Symfony handles services.
I am setting currently setting up the bucket location, rate and starting amount in the service's constructor:
public function __construct()
{
$storage = new FileStorage(__DIR__ . "/api.bucket");
$rate = new Rate(50, Rate::MINUTE);
$bucket = new TokenBucket(50, $rate, $storage);
$this->consumer = new BlockingConsumer($bucket);
$bucket->bootstrap(50);
}
I'm then attempting to consume a token before each request:
public function fetch(): array
{
try {
$this->consumer->consume(1);
$response = $this->client->request(
'GET', $this->buildQuery(), [
'query' => array_merge($this->params, ['api_key' => $this->apiKey]),
'headers' => [ 'Content-type' => 'application/json' ]
]
);
} catch (ServerException $e) {
// Process Server Exception
} catch (ClientException $e) {
// Process Client Exception
}
return $this->checkResponse($response);
}
I can't see anything obvious in that, that would allow it to request more than 50 times per minute unless the amount of available tokens was being reset on each request.
This is being supplied to a set of repository services that handle converting the data from each endpoint into objects used within the system. Consumers use the appropriate repository to request the data needed to complete their process.
If the amount of tokens is being reset by the bootstrap function being in service constructor, where should it be moved to within the Symfony framework that would still work with consumers?
I assume that it should work, but maybe try to move the ->bootstrap(50) call from every request? Not sure, but it can be the reason.
Anyway it's better to do that only once, as a part of your deployment (every time you deploy a new version). It doesn't have anything with Symfony, really, because the framework doesn't have any restrictions on deployment procedure. So it depends on how you do the deployment.
P.S. Have you considered to just handle 429 errors from the server? IMO you can wait (that's what BlockingConsumer does inside) when you receive 429 error. It's simpler and doesn't require an additional layer in your system.
BTW, have you considered nginx's ngx_http_limit_req_module as an alternative solution? It usually comes with nginx by default, so no additional actions to install, only a small configuration is required.
You can place an nginx proxy behind your code and the target web service and enable limits on it. Then in your code you will handle 429 as usual, but the requests will be throttled by your local nginx proxy, not by the external web service. So the final destination will get only limited amount of requests.
I have found a trick using Guzzle bundle for symfony.
I had to improve a sequential program sending GET requests to a Google API. In code example, it a pagespeed URL.
To have a rate limit, there an option to delay the requests before they are sent asynchronously.
Pagespeed rate limit is 200 requests per minute.
A quick calculation gives 200/60 = 0.3s per request.
Here is the code I tested on 300 urls, getting a fantastic result of no error, except if the url passed as a parameter in the GET request gives a 400 HTTP Error (Bad request).
I put a delay of 0.4s and the average result time is less then 0.2s, whereas it took more than a minute with a sequential program.
use GuzzleHttp;
use GuzzleHttp\Client;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Exception\ClientException;
// ... Now inside class code ... //
$client = new GuzzleHttp\Client();
$promises = [];
foreach ($requetes as $i=>$google_request) {
$promises[] = $client->requestAsync('GET', $google_request ,['delay'=>0.4*$i*1000]); // delay is the trick not to exceed rate limit (in ms)
}
GuzzleHttp\Promise\each_limit($promises, function(){ // function returning the number of concurrent requests
return 100; // 1 or 100 concurrent request(s) don't really change execution time
}, // Fulfilled function
function ($response,$index)use($urls,$fp) { // $urls is used to get the url passed as a parameter in GET request and $fp a csv file pointer
$feed = json_decode($response->getBody(), true); // Get array of results
$this->write_to_csv($feed,$fp,$urls[$index]); // Write to csv
}, // Rejected function
function ($reason,$index) {
if ($reason instanceof GuzzleHttp\Exception\ClientException) {
$message = $reason->getMessage();
var_dump(array("error"=>"error","id"=>$index,"message"=>$message)); // You could write the errors to a file or database too
}
})->wait();
I am a Java programmer and new with php. I am experiencing high cpu usage and long transaction times when I acces services using guzzle. Sending small message wil cost me on average half a second.
The code below will cost me 0.249 seconds
// Create the REST client
$client = new Client(URL, array(
'request.options' => array(
'auth' => array($lgUser, $lgPassword, 'Basic')
)
));
$time_start = microtime(true);
// Login to the web service
$request = $client->get('/PartnerInformation.svc/Login');
$request = $client->get('/PartnerInformation.svc/Login');
try {
$response = $request->send();
$lgSID = $response->xml();
echo ("Logged in successfully; SID: ".$lgSID);
} catch (Exception $e) {
echo ("Error while logging in: ".$e);
}
$time_end = microtime(true);
$time_total = $time_end-$time_start;
echo('login time: '.$time_total);
Are there things I can do to speed things up or find the problem?
I found out by looking into the guzzle.phar file that we are using version 3.8.1., would a transfer to a newer version boost the performance and lower the cpu usage? What kind of problems can I expect installing a new goozle version? Will it be enough to change the guzzle.phar file?
You can read about changes and breakwards in official documentation. However as I see from pasted code there is no changes.
Architecturally speaking, there are some huge differences between 3.8 and 5.2. 5.x makes more use of closures and anonymous functions. I've found it to be more resource friendly.
Guzzle, by default, will utilize libcurl. Ultimately, any performance increase observed will be marginal due to the common underpinning.
I'd recommend upgrading into the 5.x series, and possibly even start looking at the 6.x (still in development), if for nothing else then the fact that it is actively being developed and maintained.
There are some significant changes you will need to be aware of. The most prominant of these is the fact that the "lazy methods" (get, post, header, etc, etc) will perform the request and return a Response object.
I've found the Guzzle Docs a vital resource.
first off let me "warn" you that i am not a PHP developer and am pretty clueless about PHP. I'm the developer of the WCF Service in question and am trying to support the PHP developer on staff who is trying to consume this service.
He doesn't have a Stackoverflow login and is to busy beeing pissed off at WCF to type anything without profanity ;-)
Anyhow, the service is using the following security configuration:
<security mode="Message">
<message clientCredentialType="UserName" negotiateServiceCredential="true" algorithmSuite="Default" />
</security>
This means the message is encrypted over the line, i believe this requires a certificate which has been installed on the webserver and when consuming the service from .NET works without any problems at all.
We've looked at fiddler communication and suspect RequestSecurityTokenResponse to be of import. I'm suspecting a handshake where a securitytoken is requested by the client, this is generated with a GUID as reference, the value is used to encrypt the request and the GUID is send as a reference.
This is all speculation though, so far we have been unable to get the requests to even remotely look the same.
Any pointers in the right direction would be much appreciated.
So far we're trying this with WSE-PHP, which can be found through google.
EDIT:
We've been able to confirm our thoughts with Fiddler and working clients do seem to do a handshake, there are three requests (and responses) in total which seem to exchange only security information, they are calling the following actions:
http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue
http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue
http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT
After this last call a request is made with an action which seems to indicate a call to the actual webservice method:
http://XXXXXXXXXXXXXX/WCF/ICompany/TestConnection
This seems to have to do with SAML (i love you google) so i added this as a tag.
Yes, there is support for WS-Security in PHP. Some assembly is required. See, for example, Secured Web Services with PHP. The WS-* specifications are written and implemented for interoperability, that is, so that the will operate across different platforms, vendors, transports, and languages. It is widely adopted, and the 1.1 version has been an OASIS standard since 2006.
Perhaps it would help defuse the tenion and be instructive if your PHP developer would show the ability to consume any web service that uses WS-Security, just to take your service and WCF out of the equation for a little while.
After my long workout for Email WCF service with attachment. Now i am able to send an email from PHP using WCF service. Here is code snippet.
<?php
ini_set('display_errors',1);
require_once ('nusoap.php');
$parameters= new StdClass();
$parameters->emailinfo = new StdClass();
$fileAttachmentPath = 'example.csv';
$data = base64_encode(file_get_contents($fileAttachmentPath));
$parameters->emlinfo->NoFileInfo = true;
$parameters->emlinfo->FileNameWithExt = "example.csv";
$parameters->emlinfo->FileContentBase64 = $data;
//$parameters->emlinfo->FileAttachment = $parameters->ArrayOfFileAttachment;
//$parameters->emlinfo->FileAttachment = (array) $parameters->emlinfo->FileAttachment;
$parameters->emlinfo->MailTo='';
$parameters->emlinfo->MailFrom='';
$parameters->emlinfo->MailMessage='';
$parameters->emlinfo->MailBody='';
$parameters->emlinfo->MailSubject='';
$parameters->emlinfo->MailType='';
$parameters->emlinfo->UserId='';
$parameters->emlinfo->Password='';
$parameters->emlinfo->SmtpId='';
$parameters->emlinfo->Token='';
$parameters->emlinfo->ApplicationId='';
$parameters->emlinfo->VisitorName='';
$parameters->emlinfo->VendorId='';
$parameters->emlinfo->ReplyTo='';
try {
$braspag = new SoapClient('WSDL Sevice',
array(
'trace' => 1,
'exceptions' => 1,
'style' => SOAP_DOCUMENT,
'use' => SOAP_LITERAL,
'soap_version' => SOAP_1_1,
'encoding' => 'UTF-8'
)
);
//$SendEmailResponse = $braspag->__getTypes();
$SendEmailResponse = $braspag->SendEmail($parameters);
}
catch(SoapFault $fault) {
//echo 'Ocorreu um erro: ' , $fault->getMessage();
}
var_dump($parameters->emlinfo);
?>
Regards,
Rahul Soni.
Why dont you setup a platform independent UI project using something like SOAP UI where it will enable you to have automated tests against your WCF service, guaranteeing your contracts are sound while benefiting the PHP consumer. The reason this benefits the PHP consumer is that he will have automated test cases which he can profile/run/precident he can use to make his php side of things.
One thing to note is that .net namespacing can cause issues for other languages with SOAP. I really really recommend you investigate SOAP UI or a free alternative. Possibly an alternative that is free is fiddler, however I believe that is not automated.
Here are some other helpers for your PHP consumer:
http://weblogs.asp.net/gunnarpeipman/archive/2007/09/17/using-wcf-services-with-php.aspx
https://github.com/geersch/WcfServicesWithPhp5
I don't know a lot about WCF but it seems that is SOAP based. You can start by looking at php Soap functions. I see that also supports JSON communication, check JSON functions from php.
(this is a bit long to add as comment to Visual Stewart's answer)
clientCredentialType="UserName"....This means the message is encrypted over the line
Not really. The message is not encrypted, but it will be sent via SSL. From the published docs:
Username
Allows the service to require that the client be authenticated with a user name credential. Note that WCF does not allow any cryptographic operations with user names, such as generating a signature or encrypting data. WCF ensures that the transport is secured when using user name credentials.
--
Note the difference between the message and the transport.
Authentication may be derived from a client certificate used for the transport, or negotiated (NTLM).
If the server is configured with clientCredentialType="UserName" PHP consumer must use some WS-Security implementation to send Username and Password SOAP security headers to the server.
<soapenv:Envelope>
<soapenv:Header>
<wsse:Security >
<wsse:UsernameToken>
<wsse:Username>bob</ wsse:Username>
<wsse:Password Type="PasswordText">bob1</ wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body> ... </soapenvBody>
</soapenv:Envelope>
Keep in mind that the server can be configure to acept plain or hashed password. So the client must send plain or hashed password.