I'd like to get the text from the <Version> element which is nested inside the <service> block of a WSDL. The WSDL in question is Ebay's Trading api. The snippet in question looks something like this:
<wsdl:service name="eBayAPIInterfaceService">
<wsdl:documentation>
<Version>941</Version>
</wsdl:documentation>
<wsdl:port binding="ns:eBayAPISoapBinding" name="eBayAPI">
<wsdlsoap:address location="https://api.ebay.com/wsapi"/>
</wsdl:port>
</wsdl:service>
I'm currently doing this:
$xml = new DOMDocument();
$xml->load($this->wsdl);
$version = $xml->getElementsByTagName('Version')->item(0)->nodeValue;
This works but I'm wondering if there is a method to get this natively using PHP's SOAP extension?
I was thinking something like the following would work but it doesn't:
$client = new SoapClient($this->wsdl);
$version = $client->eBayAPIInterfaceService->Version;
It is not possible to do what you want with the regular SoapClient. Your best bet is to extend the SoapClient class and abstract-away this requirement to get the version.
Please beware that file_get_contents is not cached so it will always load the WSDL file. On the other hand SoapClient caches the WSDL so you will have to deal with it yourself.
Perhaps look into NuSOAP. You will be able to modify the code to suit your purposes without loading the WSDL twice (of course you are able to modify SoapClient too but that's another championship ;) )
namespace Application;
use DOMDocument;
class SoapClient extends \SoapClient {
private $version = null;
function __construct($wsdl, $options = array()) {
$data = file_get_contents($wsdl);
$xml = new DOMDocument();
$xml->loadXML($data);
$this->version = $xml->getElementsByTagName('Version')->item(0)->nodeValue;
// or just use $wsdl :P
// this is just to reuse the already loaded WSDL
$data = "data://text/plain;base64,".base64_encode($data);
parent::__construct($data, $options);
}
public function getVersion() {
return is_null($this->version) ? "Uknown" : $this->version;
}
}
$client = new SoapClient("http://developer.ebay.com/webservices/latest/ebaysvc.wsdl");
var_dump($client->getVersion());
Have you tried simplexml_load_file? Worked for me when i needed to parse an XML-File with php.
<?php
$file = "/path/to/yourfile.wsdl";
$xml = simplexml_load_file($file) or die ("Error while loading: ".$file."\n");
echo $xml->service->documentation->Version;
//if there are more Service-Elements access them via index
echo $xml->service[index]->documentation->Version;
//...where index in the number of the service appearing
//if you count them from top to buttom. So if "eBayAPIInterfaceService"
//is the third service-Element
echo $xml->service[2]->documentation->Version;
?>
Related
I am trying to add a child note to an XML file using PHP. I have used the same method about 5 times before and each one works as expected, however this method is using a different XML structure on a new file (which I think might be part of the problem).
The method causing me the problem is this:
function addCourseToProject($name, $project){
$projects = self::getRoles();
$theProject = $projects->xpath('project[name = "'.$project.'"]');
echo $theProject->name;
$theCourses = $theProject[0]->courses->addchild("course", $name);
$projects->asXml("projects.xml");
}
The XML file looks like this:
<projects>
<project>
<name>Alpha</name>
<courses>
<course>Beta</course>
<course>Charlie</course>
</courses>
</project>
</project>
And getRoles() looks like this:
function getRoles(){
$roles = simplexml_load_file("projects.xml") or die("ERROR: Unable to read file");
return $roles;
}
I'm at a loss here, I have no idea why this is different from the other adding functions (see below).
Should you need it, here is an example of what my other methods look like:
function addModule($courseName, $name, $link){
$xml = self::getXml();#get the xml file
$course = $xml->xpath('course[name = "'.$courseName.'"]');
$module = $course[0]->addChild("module");
$module->addChild("name", $name);
$module->addChild("link", $link);
$xml->asXml("data.xml");
echo self::toJSON($course);
}
and the getXML() method:
function getXml(){
$xml=simplexml_load_file("data.xml") or die("ERROR: Unable to read file");
return $xml;
}
Turned out that the problem was caused by the fact that <project> element is nested within the XML document. You can use descendant-or-self axis (// for the abbreviated syntax) to get nested element like so :
$theProject = $projects->xpath('//project[name = "'.$project.'"]');
or specify the complete path :
$theProject = $projects->xpath('/projects/project[name = "'.$project.'"]');
I'm sending the following XML to an api using cURL:
$xml = "<request type='auth' timestamp='$timestamp'>
<merchantid>$merchantid</merchantid>
<account>$account</account>
<orderid>$orderid</orderid>
<amount currency='$currency'>$amount</amount>
<card>
<number>$cardnumber</number>
<expdate>$expdate</expdate>
<type>$cardtype</type>
<chname>$cardname</chname>
</card>
<sha1hash>$sha1hash</sha1hash>
</request>";
What is the best way to avoid hard coding this XML? I was thinking of using XMLWriter but seems strange as it won't be changing.
Should I use a template? Or generate it using XMLWriter / Simple XML?
As I mentioned in the comments, there's not necessarily a right answer to this but I recently had to write a project around an XML API Feed as well. I decided to go with XMLWriter and it's still very easy to interchange into others easily by using their respected .loadXML() functions.
class SomeApi extends XMLwriter {
public function __construct() {
$this->openMemory();
$this->setIndent( true );
$this->setIndentString ( " " );
$this->startDocument( '1.0', 'UTF-8', 'no' );
$this->startElement( 'root' );
}
public function addNode( $Name, $Contents ) {
$this->startElement( $Name );
$this->writeCData( $Contents );
$this->endElement();
}
public function output() {
$this->endElement();
$this->endDocument();
}
//Returns a String of Xml.
public function render() {
return $this->outputMemory();
}
}
$newRequest = new SomeApi();
$newRequest->addNode( 'some', 'Some Lots of Text' );
$Xml = $newRequest->render();
I think it's a nice clean way writing an XML Feed in PHP, furthermore as you can add internal functions such as:
$this->addHeader();
private function addHeader() {
$this->addNode( 'login', 'xxxxx' );
$this->addNode( 'password', 'xxxxx' );
}
Which then appends nodes that you'll use over & over again. Then if you suddenly need to use a DOMDocument object for example (As I needed too for XSL).
$Dom = new DOMDocument();
$Dom->loadXML( $Xml );
Should I use a template?
You actually already did use a template here.
Or generate it using XMLWriter / Simple XML?
XMLWriter and also SimpleXMLElement are components that allow you to create XML easily. For your specific case I'd use SimpleXML for a start:
$xml = new SimpleXMLElement('<request type="auth"/>');
$xml['timestamp'] = $timestamp;
$xml->merchantid = $merchantid;
$xml->account = $account;
$xml->orderid = $orderid;
$xml->addChild('amount', $amount)['currency'] = $currency;
$card = $xml->addChild('card');
$card->number = $cardnumber;
$card->expdate = $expdate;
$card->type = $cardtype;
$card->chname = $cardname;
$xml->sha1hash = $sha1hash;
See that the XML is not hardcoded any longer, only the names used are. The SimpleXML library takes care to create the XML (demo, here the output is beautified for better readability):
<?xml version="1.0"?>
<request type="auth" timestamp="">
<merchantid></merchantid>
<account></account>
<orderid></orderid>
<amount currency=""/>
<card>
<number></number>
<expdate></expdate>
<type></type>
<chname></chname>
</card>
<sha1hash></sha1hash>
</request>
Thanks to the library, the output is always valid XML and you don't need to care about the details here. You can further simplify it by wrapping it more, but I don't think this is of much use with your very little XML you have here.
In PHP I would like to know what will be the method called by SOAP. Here is a sample to understand...
$soapserver = new SoapServer();
$soapserver->setClass('myClass');
$soapserver->handle();
What I would like to know is the name of the method that will be executed in handle()
Thank you !!
In my opinion, the cleanest and most elegant way to access the called operation's name in this situation would be using some kind of Wrapper or Surrogate design pattern. Depending on Your intent You would use either the Decorator or the Proxy.
As an example, let's say We want to dynamically add some additional functionality to our Handler object without touching the class itself. This allows for keeping the Handler class cleaner and, thus, more focused on its direct responsibility. Such functionality could be logging of methods and their parameters or implementing some kind of caching mechanism. For this We will use the Decorator design pattern. Instead of doing this:
class MyHandlerClass
{
public function operation1($params)
{
// does some stuff here
}
public function operation2($params)
{
// does some other stuff here
}
}
$soapserver = new SoapServer(null, array('uri' => "http://test-uri/"));
$soapserver->setClass('MyHandlerClass');
$soapserver->handle();
We'll do the following:
class MyHandlerClassDecorator
{
private $decorated = null;
public function __construct(MyHandlerClass $decorated)
{
$this->decorated = $decorated;
}
public function __call($method, $params)
{
// do something with the $method and $params
// then call the real $method
if (method_exists($this->decorated, $method)) {
return call_user_func_array(
array($this->decorated, $method), $params);
} else {
throw new BadMethodCallException();
}
}
}
$soapserver = new SoapServer(null, array('uri' => "http://test-uri/"));
$soapserver->setObject(new MyHandlerClassDecorator(new MyHandlerClass()));
$soapserver->handle();
If You want to control the access to the Handler's operations, for instance, in order to impose access rights use the Proxy design pattern.
I know this is an old post, but someone could make use of this solution. It should be possible to extract data from raw HTTP POST data. You cannot use $_POST, because it's empty but you can use predefined variable $HTTP_RAW_POST_DATA which contains string with SOAP request in XML format.
The method name should be in first node of <soapenv:Body> tag like this:
<!--
...
XML header, SOAP header etc.
...
-->
<soapenv:Body>
<urn:methodName soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<param1 xsi:type="xsd:string" xs:type="type:string" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance">param1 value</param1>
<param2 xsi:type="xsd:string" xs:type="type:string" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance">param2 value</param2>
</urn:methodName>
</soapenv:Body>
<!--
...
-->
You could probably parse it with something like SimpleXML or maybe use some regular expresion to get methodName but remember that the string urn: is a namespace defined in the header and therefore it could be anything.
Although not the nicest way, you can use this http://danpolant.com/use-the-output-buffer-to-debug-a-soap-server/ somehow.
For the quick and very dirty approach (please use this only for a one-time debug and not in production code!): just assign a global variable with the name of each SOAP method in the method bodies and do whatever you want with it after the SoapServer does its job, as described in the above link. Something like this (untested code):
$method = "";
class test
{
function call1()
{
global $method; $method = "call1";
}
}
ob_start();
$soapserver = new SoapServer();
$soapserver->setClass('test');
$soapserver->handle();
$mystring = ob_get_contents(); // retrieve all output thus far
ob_end_clean (); // stop buffering
log($mystring); // log output
log($method); // log method
echo $mystring; // now send it
Usually (but not always, depends on the client) $_SERVER['HTTP_SOAPACTION'] is set and you can get the name of the called method from it.
I've worked on this code base and it responds with html when i access a site www.site.com/version/
However, If i access www.site.com/version?format=xml, it displays output in xml.
How can I change the Zend code to ONLY output in XML irrespective of the format request? Yeah, i'm new to Zend coding...)
My code is exactly what Chris has ( http://www.chrisdanielson.com/2009/09/02/creating-a-php-rest-api-using-the-zend-framework/ ) :
class VersionController extends Zend_Rest_Controller
{
public function init()
{
$bootstrap = $this->getInvokeArg('bootstrap');
$options = $bootstrap->getOption('resources');
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addActionContext('index', array('xml','json'))->initContext();
//$this->_helper->viewRenderer->setNeverRender();
$this->view->success = "true";
$this->view->version = "1.0";
}
...
...
You can force the context to use XML only by following code:
$this->_helper->contextSwitch()->initContext('xml');
Refer link:
http://framework.zend.com/manual/en/zend.controller.actionhelpers.html#zend.controller.actionhelpers.contextswitch.initcontext
I'm working on a new class to wrap XML handling. I want my class to use simplexml if it's installed, and the built in XML functions if it's not. Can anyone give me some suggestions on a skeleton class to do this? It seems "wrong" to litter each method with a bunch of if statements, and that also seems like it would make it nearly impossible to correctly test.
Any upfront suggestions would be great!
EDIT: I'm talking about these built-in xml functions.
Which built-in xml functions are you referring to? SimpleXml is a standard extension, which uses libxml underneath - just as the dom extension does. So if the dom extension is installed, chances are that so is SimpleXml.
I've made a class which wraps SimpleXml functionality... take what you may from it...
bXml.class.inc
There is one weird thing... it's that SimpleXml doesn't allow its constructor to be overloaded, so you can't do things at initiation ... like override the input value (i.e. so you can accept XML as in input). I got around that limitation by using an ArrayObject class to wrap the new SimpleXml class.
I use something like this for doing xml translations and content:
Assuming xml structure something like this (important to use a regular structure, means you can pull off some nice agile tricks!):
<word name="nameofitem">
<en>value</en>
<pt>valor</pt>
<de>value_de</de>
</word>
and then a class to handle the xml:
class translations
{
public $xml = null;
private $file = null;
private $dom = null;
function __construct($file="translations") {
// get xml
$this->file = $file;
$this->haschanges = false;
$this->xml = file_get_contents($_SERVER['DOCUMENT_ROOT']."/xml/".$file.".xml");
$this->dom = new DOMdocument();
$this->dom->loadXML($this->xml);
}
function updateNode($toupdate, $newvalue, $lang="pt",$rootnode="word"){
$this->haschanges = true;
$nodes = $this->dom->getElementsByTagName($rootnode);
foreach ($nodes as $key => $value) {
if ($value->getAttribute("name")==$toupdate) {
$nodes->item($key)->getElementsByTagName($lang)->item(0)->nodeValue = htmlspecialchars($newvalue,ENT_QUOTES,'UTF-8');
}
}
}
function saveUpdated(){
$toSave = $this->dom->saveXML();
if ($this->haschanges === true) {
file_put_contents($_SERVER['DOCUMENT_ROOT']."/xml/".$this->file.".xml", $toSave);
return true;
}
else {
return false;
}
}
}
I took out a few of the methods I have, for brevity, but I extend this with things to handle file and image uploads etc too.
Once you have all this you can do:
$xml = new translations();
// loop through all the language posts
foreach ($_POST["xml"]["en"] as $key => $value) {
$xml->updateNode($key, stripslashes($value), "en");
}
Or something ;) hope this gives you some ideas!