In PHP, we have SoapVar and SoapParam classes. I've been confused long enough, since there is no decent documentation of them on php.net.
Today I was surprised to find out that those lines will produce the exact same result in the XML input:
$soapVar = new SoapVar((object) ['Foo' => 'bar'], null, null, null, 'TagName');
$soapParam = new SoapParam((object) ['Foo' => 'bar'], 'TagName');
In most SoapClient tutorials, I have seen snippets with SoapParam inside SoapVar whenever someone wanted to set custom xsi:type:
$response = $soapClient->DoSomething(
new SoapParam(
new SoapVar(
(object) ['Foo' => 'bar'],
null,
'TypeName'
),
'TagName'
)
);
That is really unintuitive, since SoapVar and SoapParam class names don't say much. The same result can be achieved by more elegant:
$response = $soapClient->DoSomething(
new SoapVar(
(object) ['Foo' => 'bar'],
null,
'TypeName',
null,
'TagName'
)
);
What is the purpose of SoapParam then? Is it just more simple version of SoapVar? It seems like these two are confused and misunderstood a lot. Is there any extra behavior attached to SoapParam?
You are right the manual is a little vague explaining the exact difference in theory and I was surprised that till now none had pointed some detailed info or explanation into their differences.
By the manual
SoapParam:
Represents parameter to a SOAP call.
SoapVal:
A class representing a variable or object for use with SOAP services.
I went a little down and the main difference I could find was the data parameter in their representative constructors:
for SoapParam::SoapParam ( mixed $data , string $name )
where $data =
The data to pass or return. This parameter can be passed directly as
PHP value, but in this case it will be named as paramN and the SOAP
service may not understand it.
while on the other hand we have SoapVar::SoapVar ( mixed $data , string $encoding [, string $type_name [, string $type_namespace [, string $node_name [, string $node_namespace ]]]] ) where $data =
The data to pass or return.
By taking these two into consideration, the example in the PHP Manual, i believe points for good the main difference in the argument passing and what you can do with one that can not be done with the other.
In the case of SoapParam
<?php
$client = new SoapClient(null,array('location' => "http://localhost/soap.php",
'uri' => "http://test-uri/"));
$client->SomeFunction(new SoapParam($a, "a"),
new SoapParam($b, "b"),
new SoapParam($c, "c"));
?>
where $a, $b and $c are PHP variables send directly without passing them through a structure / class. The SaopParam constructor is getting the parameter $data (PHP Variable) and the String representative of the parameter name.
On the other side SoapVar
<?php
class SOAPStruct {
function SOAPStruct($s, $i, $f)
{
$this->varString = $s;
$this->varInt = $i;
$this->varFloat = $f;
}
}
$client = new SoapClient(null, array('location' => "http://localhost/soap.php",
'uri' => "http://test-uri/"));
$struct = new SOAPStruct('arg', 34, 325.325);
$soapstruct = new SoapVar($struct, SOAP_ENC_OBJECT, "SOAPStruct", "http://soapinterop.org/xsd");
$client->echoStruct(new SoapParam($soapstruct, "inputStruct"));
?>
As you can see, the arguments/variables are passed through the per-defined class and after an object of that class is passed as argument. That Class is a representative of the SOAP Structure (that SOAP can recognize / thous the risk with SoapParam).
Now the almost confusing part...although there are a few differences in the constructor why the same behavior on your example?
$soapVar = new SoapVar((object) ['Foo' => 'bar'], null, null, null, 'TagName');
$soapParam = new SoapParam((object) ['Foo' => 'bar'], 'TagName');
I am not surprised, as mentioned above the data either a SOAP Structure or a PHP Variable of a CORRECT Soap Structure is the same :) thanks to Object Oriented we pass the Object produced by a PHP Class. In the SoapVar the construction of the object has more options, thus allowing the user to adapt a more "safe" request towards the SOAP call. While on SoapParam the object can be constructed by passing any object and next its name.
Your (object) ['Foo' => 'bar'] is very simple and has no need for XML Schema Encoding thus the SOAP call receiving it I believe can very easily understand it. It has no need for the
encoding
The encoding ID, one of the XSD_... constants.
that is the second parameter in the SoapVar constructor.
Finally since they both accept an object (any object) in place of the $data variable to my understanding the SoapVar just gives the possibility to build a more safer request to be understood by the SOAP call through the extra arguments in the constructor. The SoapParam is just a badass and allows you to send anything you like at the risk of the SOAP refusing it.... it's like almost wants to act as JSON # RESTful :P not caring about the type nor structure of the data it sends :)
References:
SoapParam Class
SoapParam Constructor
SoapVar Class
SoapVar Constructor
Related
Given a variable that holds this string:
$property = 'parent->requestdata->inputs->firstname';
And an object:
$obj->parent->requestdata->inputs->firstname = 'Travis';
How do I access the value 'Travis' using the string? I tried this:
$obj->{$property}
But it looks for a property called 'parent->requestdata->inputs->firstname' not the property located at $obj->parent->requestdtaa->inputs->firstname`
I've tried various types of concatenation, use of var_export(), and others. I can explode it into an array and then loop the array like in this question.
But the variable '$property' can hold a value that goes 16 levels deep. And, the data I'm parsing can have hundreds of properties I need to import, so looping through and returning the value at each iteration until I get to level 16 X 100 items seems really inefficient; especially given that I know the actual location of the property at the start.
How do I get the value 'Travis' given (stdClass)$obj and (string)$property?
My initial searches didn't yield many results, however, after thinking up a broader range of search terms I found other questions on SO that addressed similar problems. I've come up with three solutions. All will work, but not all will work for everyone.
Solution 1 - Looping
Using an approach similar to the question referenced in my original question or the loop proposed by #miken32 will work.
Solution 2 - anonymous function
The string can be exploded into an array. The array can then be parsed using array_reduce() to produce the result. In my case, the working code (with a check for incorrect/non-existent property names/spellings) was this (PHP 7+):
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
$property = 'parent->requestdata->inputs->firstname';
$name = array_reduce(explode('->', $property), function ($previous, $current) {
return is_numeric($current) ? ($previous[$current] ?? null) : ($previous->$current ?? null); }, $obj);
var_dump($name); //outputs Travis
see this question for potentially relevant information and the code I based my answer on.
Solution 3 - symfony property access component
In my case, it was easy to use composer to require this component. It allows access to properties on arrays and objects using simple strings. You can read about how to use it on the symfony website. The main benefit for me over the other options was the included error checking.
My code ended up looking like this:
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
//don't forget to include the component at the top of your class
//'use Symfony\Component\PropertyAccess\PropertyAccess;'
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
//NOTE: syfony uses dot notation. I could not get standard '->' object notation to work.
$property = 'parent.requestdata.inputs.firstname';
//create symfony property access factory
$propertyAccessor = PropertyAccess::createPropertyAccessor();
//get the desired value
$name = $propertyAccessor->getValue($obj, $property);
var_dump($name); //outputs 'Travis'
All three options will work. Choose the one that works for you.
You're right that you'll have to do a loop iteration for each nested object, but you don't need to loop through "hundreds of properties" for each of them, you just access the one you're looking for:
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
$property = "parent->requestdata->inputs->firstname";
$props = explode("->", $property);
while ($props && $obj !== null) {
$prop = array_shift($props);
$obj = $obj->$prop ?? null;
}
var_dump($obj);
Totally untested but seems like it should work and be fairly performant.
I am absolutely new in PHP and moreover in Laravel framework (I don't know if Laravel provides some utility class for this kind of tasks). I came from Java.
So I have the following problem:
Into a class I perform a call to a REST web service, something like this:
$response = $client->get('http://localhost:8080/Extranet/login',
[
'auth' => [
'dummy#gmail.com',
'pswd'
]
]);
$dettagliLogin = json_decode($response->getBody());
\Log::info('response: '.(json_encode($dettagliLogin)));
$response->getBody() contains the returned JSON object, this is the output of the previous \Log::info():
{
"id":5,
"userName":"Dummy User",
"email":"dummy#gmail.com",
"enabled":true
}
So I have the following problems:
1) What exactly returns the json_decode() function? I really can't understand because PHP is not strongly typed and I have not a declared return type.
This is the method signature:
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
and in the related doc it says #return mixed. What exactly means "mixed"?
2) Anyway the main problem is: I have to use the content of the previous returned JSON object and put these value into the related field of an array like this:
$attributes = array(
'id' => HERE THE id FIELD VALUE OF MY JSON OBJECT,
'username' => HERE THE email FIELD VALUE OF MY JSON OBJECT',
'name' => HERE THE userName FIELD VALUE OF MY JSON OBJECT,
);
So I think that I have to parse the value of the $response->getBody() or of the json_decode($response->getBody()) to obtain these values. But how exactly can I do it? What is the neater way to do it? Does the Laravel framework provide some utility to do it?
For better understanding, let's first describe - what's JSON?
It's a way of representing objects (arrays, objects, etc) in a string.
1) What exactly returns the json_decode() function? I really can't
understand because PHP is not strongly typed and I have not a declared
return type. This is the method signature:
function json_decode($json, $assoc = false, $depth = 512, $options =
0) and in the related doc it says #return mixed. What exatly means
mixed?
json_deocde converts the JSON string into the original "structure" it represent.
#return mixed means that the returned value of json_decode can be any type of variable. If the JSON represent an array - it would be an array type, if it represent an object - it would be an object type.
2) Anyway the main problem is: I have to use the content of the
previous returned JSON object and put these value into the related
field of an array like this:
$attributes = array(
'id' => HERE THE id FIELD VALUE OF MY JSON OBJECT,
'username' => HERE THE email FIELD VALUE OF MY JSON OBJECT',
'name' => HERE THE userName FIELD VALUE OF MY JSON OBJECT,
);
In order to make sure which type of variable your JSON represent, you can use var_dump(json_decode($json));. Anyway, it's a class object.
Therefore:
$object = json_decode($json);
$attributes = array(
'id' => $object->id,
'username' => $object->email,
'name' => $object->userName,
);
If you json string is an object (not an array) it will return an object (of type stdClass). Mixed means it can be multiple things, so if it was a json array, you'd get an array.
Best thing to do is use json_decode, and then var_dump (or var_export) to see what you actually get.
I am working on something where I need to be able to pass an indexed array of args to a method, much like how call_user_func_array works. I would use call_user_func_array but it is not an OOP approach, which is undesired, and it requires the method to be static, which breaks the target class's OO.
I have tried to use ReflectionClass but to no avail. You cannot invoke arguments to a method of the class, only the constructor. This is unfortunately, not desireable.
So I took to the man pages and looked at ReflectionFunction but there is no way to instantiate the class, point it to a method, and then invokeArgs with it.
Example using ReflectionFunction ( remember, this question is tagged PHP 5.4, hence the syntax):
$call = new \ReflectionFunction( "(ExampleClass())->exampleMethod" );
$call->invokeArgs( ["argument1", "argument2"] );
This fails with:
Function (Index())->Index() does not exist
Example using ReflectionMethod
$call = new \ReflectionMethod( "ExampleClass", "exampleMethod" );
$call->invokeArgs( new ExampleClass(), ["argument1", "argument2"] );
print_r( $call );
This fails with:
ReflectionMethod Object
(
[name] => Index
[class] => Index
)
The arguments are never passed to the method.
The desired results are:
class ExampleClass() {
public function exampleMethod( $exampleArg1, $exampleArg2 ){
// do something here
echo "Argument 1: {$exampleArg1}\n";
echo "Argument 2: {$exampleArg2}\n";
}
}
$array = [ 'exampleArg1Value', 'exampleArg2Value' ];
If I passed $array to an instance of ExampleClass->exampleMethod(), I would only have one argument, which would be an array. Instead, I need to be able to pull the individual arguments.
I was thinking that if there was a way to call ReflectorFunction on a ReflectorClass I would in in ship-shape and on my way, but it doesn't look like that is possible.
Does anyone have anything they have used to accomplish this previously?
AFAIK, the following should work:
$call = new \ReflectionMethod( "ExampleClass", "exampleMethod" );
$call->invokeArgs( new ExampleClass(), ["argument1", "argument2"] );
print_r( $call );
What minor version is PHP? Are you on 5.4.7?
I have written my own dependency injector, and I also construct classes with the parameters dynamicly. Here is some code that should get your going:
$type = 'ExampleClass';
$reflector = new \ReflectionClass( $type );
if ( !$reflector->isInstantiable() )
throw new \Exception( "Resolution target [$type] is not instantiable." );
$constructor = $reflector->getConstructor();
$parameters = $constructor->getParameters();
At this point you have a array of parameters, needed for construction. You can now substitute the parameters with the values and construct the class.
For some reason, something got stuck, somewhere.
$call = new \ReflectionMethod( "ExampleClass", "exampleMethod" );
$call->invokeArgs( new ExampleClass(), ["argument1", "argument2"] );
Now returns
Argument 1: argument1
Argument 2: argument2
I am going to try to reproduce the issue. It is on a fresh php 5.4.7 install with php-cli and fpm.
I'm having problems creating proper variables according to my WebService WSDL. I have implemented this simple feature in python succesfully using suds 0.4 SOAP library.
Python implementation (tracker is my SOAP client object that consumes wsdl):
c = self.tracker.factory.create("ns4:Text")
c.type = "text/html"
c.content = "my content goes here"
self.tracker.service.createComment(c)
How do I implement this in PHP? At first glance I didn't quite understand how to archieve this with the PHP SOAP extension. The "...factory.create("ns4:Text") seems convinient in python. I can examine the attributes of the object and easily pass it to my available functions.
Do I really need to define the object in PHP the following way:
$c->type = "text/html";
$c->content = "my content goes here";
$this->tracker->__soapCall('createComment',array($c));
This implementation expects that I know and will define all the attributes of the object. I have complex data types of +37 attributes, also nested. Only 4 of them are required, and I would like to pass it to server with only 4 attributes filled, but still as a complete object with all the attributes defined...?
Does this make any sense?
In summary: python creates me complete object from the wsdl file, how do I get this in PHP?
PHP can use the WSDL file to generate an appropriate set of methods to which you can pass generic objects, arrays, or scalars as arguments. You can also specify which classes map to which methods (the classmap option), and which type declarations map to which serialization callback functions (the typemap option) by using the second parameter of the SoapClient class.
class doRequestMethod {
public $id;
public $attribute;
}
class theResponseClass {
/* ... */
}
$options = array(
'classmap' => array(
'doRequest' => 'doRequestMethod',
'theResponse' => 'theResponseClass'
/* ... */
),
'typemap' => array(
0 => array(
'type_ns' => 'http://example.com/schema/wsdl_type.xsd',
'type_name"' => 'wsdl_type',
'from_xml' => function ($xml_string) { /* ... */ },
'to_xml' => function ($soap_object) { /* ... */ }
)
/* ... */
)
)
$client = new SoapClient('/path/to/filename.wsdl', $options);
$request = new doRequestMethod();
$request->id = 0;
$request->attribute = "FooBar";
$result = $client->doRequest($request);
/*
* If 'dorequest' returns a 'theResponse' in the WSDL,
* then $result should of the type 'theResponseClass'.
*/
assert(get_class($result) === 'theResponseClass');
It's a lot of work, so I'd recommend subclassing SoapClient for your own use. Also, in order to make the code easier to debug, use PHP type-hinting on function and parameter arguments as often as possible. It prevents a whole class of bugs and it's worth the minor performance penalty.
I am consuming a WSDL in PHP with the default SoapClient object. Inside that WSDL is defines an object called Favorite that has 5 members. Is there a way I can create and instance of the class in PHP as some fo the method of that WSDL required me to pass that object to it. I have tried:
$favorite = new Favorite();
after I have comsumed the WSDL but that did not work.
You can't create the class directly. Usually, you can simply create an associative array with the correct properties for the object and the PHP SoapClient will do the rest. Otherwise, use SoapVar:
$data = array(
"abc" => 123,
"xyz" => 456,
);
$ns = "http://example.com/soap/namespace";
$var = new SoapVar($data, SOAP_ENC_OBJECT, "Favorite", $ns);