I am calling a webservice using soap in php but I am getting the error in xml as response from the server.
The problem is that when creating the xml for the request Php introduces the id in the xml and then wherever it finds the same node it just passes the id as the reference.
Eg:-
<ns1:ChargeBU id=\"ref1\">
<ns1:ChargeBreakUp>
<ns1:PriceId>0</ns1:PriceId>
<ns1:ChargeType>TboMarkup</ns1:ChargeType>
<ns1:Amount>35</ns1:Amount>
</ns1:ChargeBreakUp><ns1:ChargeBreakUp>
<ns1:PriceId>0</ns1:PriceId>
<ns1:ChargeType>OtherCharges</ns1:ChargeType>
<ns1:Amount>0.00</ns1:Amount>
</ns1:ChargeBreakUp>
</ns1:ChargeBU>
and then when it finds the same node it does this
<ns1:ChargeBU href=\"#ref1\"/>
So how can i prevent this so that it includes the full node again instead of just passing the reference ??
I had the same issue but couldn't figure out anything to do differently within SoapClient to fix it. i ended up overriding __doRequest() to modify the xml before sending to remove the reference id's from the elements and replace the reference elements with the elements they reference. if you do this, be sure to fix __getLastRequest() as shown here.
Edit: Using unserialize(serialize($input)) before sending seems to have fixed this for me.
you can create a new copy (instance) of that array to prevent php to use refs for the same values.
for example, we have:
$item = array(
"id" => 1,
"name" => "test value"
);
and our request/response:
$response = array(
"item1" => $item,
"item2" => $item
);
by default, php will replace item2 value with reference to item1 (both items point to the same array)
in order to prevent such behaviour, we need to create two different items with the same structure, something like:
function copyArray($source){
$result = array();
foreach($source as $key => $item){
$result[$key] = (is_array($item) ? copyArray($item) : $item);
}
return $result;
}
and request/response:
$response = array(
"item1" => copyArray($item),
"item2" => copyArray($item)
);
the same by structure items are in fact different arrays in memory and php will not generate any refs in this case
I did some research, and SOAP extension, nuSOAP, WSO2 are not supported since 2010. They are full of unfixed bugs, I don't recommend to use them.
Use Zend 2 SOAP instead which does not use any unsupported extension, or if you are a Symfony fan, then try out the BeSimple SOAP boundle, which tries to fix the bugs of the SOAP extension. Don't reinvent the wheel!
I have changed the function a little bit because. if the $source is not an array ,we have a small problem in the foreach
function copyArray(Array $source){
$result = array();
if($source) { // not for empty arrays
foreach($source as $key => $item){
$result[$key] = (is_array($item) ? copyArray($item) : $item);
}
}
return $result;
}
Related
I would like to allow certain graphQl operations only for certain api users based on a confguration. Our stack is Symfony 6.2 + overblog/GraphQLBundle.
My current approach is to check in the authenticate method of a custom authenticator, if the current operation is cleared in the allowed operations config of the user. For this I would like to parse the graphql query into a kind of array, that I can interpret easily.
Anybody knows how this can be done? I was scanning the underlying webonyx/graphql-php library, but can not see how they do it.
As a simple example:
query myLatestPosts($followablesToFilter: [FollowableInput], $limit: Int, $offset: Int) {
my_latest_posts(followablesToFilter: $followablesToFilter, limit: $limit, offset: $offset) {
...PostFragment
__typename
}
my_roles
}
From this I would like to retrieve the operations my_latest_posts and my_roles.
Update 1
it's probably possible to write a simple lexer utilising preg_split - I'm just hesitating, as I'm sure someone has done this already... The spec appears to be well defined.
Alright, so it turned out webonyx/graphql-php has indeed all the lowlevel function needed for this :D. Especially the Visitor is very useful here.
With this code you can drill down the query to get the operation names (or selections, as they are called in the code)
This works for me:
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Parser;
use GraphQL\Language\Visitor;
// whatever comes before
// ...
$graphQlRequest = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
$operations = [];
Visitor::visit(Parser::parse($graphQlRequest['query']), [
NodeKind::OPERATION_DEFINITION => function ($node) use (&$operations) {
// $node contains the whole graphQL query in a structured way
$selections = array_map(function ($selection) {
return $selection['name']['value'];
}, $node->toArray()['selectionSet']['selections']);
foreach ($selections as $selection) {
$operations[] = $selection;
}
return Visitor::stop();
}]
);
print_r($operations);
The result of $operations is then for my example above:
Array
(
[0] => my_latest_posts
[1] => my_roles
)
And this information is all I need to decide weather the user should have access to the endpoint or not.
I have a Laravel site I am modifying, but there are some parts of the PHP code I don't quite understand, which are "array objects" or "object arrays". You see, I don't even know what to call them and so can't find a tutorial or basic data on it. Below is the code that I am dealing with:
private function parseMetric($result, $view)
{
$data = collect([]);
$result->each(function($item) use ($data, $view) {
if (isset($item->metric->{$view})) {
$data->push((object)[
'label' => $item->metric->{$view},
'value' => $item->metric->count
]);
}
});
...
From what I can tell, this creates an object out of $result. If I json_encode this and echo it out I get this:
[{"label":"1k-25k","value":14229},
{"label":"1mm+","value":1281},
{"label":"25k-50k","value":398},
{"label":"50k-75k","value":493},
{"label":"75k-100k","value":3848},
{"label":"100k-150k","value":9921},
{"label":"150k-200k","value":4949},
{"label":"200k-250k","value":3883},
{"label":"250k-300k","value":2685},
{"label":"300k-350k","value":2744},
{"label":"350k-500k","value":4526},
{"label":"500k-1mm","value":8690}]
Now this is obviously an array of arrays... or is it? Is it an array of objects? Or is it an object containing arrays? But the most important question is, how do I access and move or change the individual objects/arrays in this object? For example, I want to take the second object/array, which is:
{"label":"1mm+","value":1281}
and move it to the end. How do I do that? How do I find it? I used the following piece of code to find it which is pretty clunky:
$pos = strpos(json_encode($result), '1mm+');
if($pos){
Log::debug('Enrich 73, I found it!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
And once I find it, how do I move that array/object to the end of the whole object?
And finally, where can I find some kind of tutorial, or documentation, that describes this construct and how to work with it?
There is no need to json_encode the data. Since the data is an instance of Laravel Collection, you can manipulate it like so
$item = $data->firstWhere('label', '1mm+'); // get the item
$data = $data->filter(fn($value, $key) => $value->label !== '1mm+') // remove $item from $data
->push($item); // move $item to the end of data
Acording to Laravel documnentation for Collections, you can try something like this :
To find index of element with name = "1mm+" :
$index = $datas->search(function ($item, $key) {
return $item['name'] == "1mm+";
});
to get an element at a given index :
$element = $datas->get($index);
to Move element at index 3 to the end :
$index = 3
$elementToMove = $data->splice($index, 1);
$datas->push($elementToMove);
Here is a link to the document used : https://laravel.com/docs/8.x/collections
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'm using an external class (Zebra_cURL) to execute multiple HTTP GET requests. It worked in the following way:
$items = array(
0=>array('url' => 'url0'),
1=>array('url' => 'url1'),
2=>array('url' => 'url2'),
3=>array('url' => 'url3'),
);
$curl = new Zebra_cURL();
$curl->get(array_column($urls,'url'),'scan_item',$moreimfo);
function scan_item($result,$moreimfo){
$items[$key]['size'] = strlen($result->body);
}
So my callback should fill up my $items array with more info for each url (in my case - size of the page). So there is a missing $key variable.
This class supports extra parameters in the callbacks ($moreimfo in my case). BUT as I understand the data passing to each callback will be always the same.
$result object containing the original url info ($result->info['url']). So I COULD use it to find needed array element. However this looks too inefficient in case the size of an array will be big enough.
I think that I should find how to pass an array member key information for EACH callback execution. Is it possible without modifying the original class?
If you use the url as key in the $items array the solution could be something like
<?php
$items = [
'url0'=>array('url' => 'url0'),
'url1'=>array('url' => 'url1'),
'url2'=>array('url' => 'url2'),
'url3'=>array('url' => 'url3'),
];
$curl = new Zebra_cURL();
$curl->get(
array_keys($items),
function($result) use (&$items) {
$key = $result->info['url'];
$items[$key]['size'] = strlen($result->body);
}
);
using an anymous function that "Imports" the $items array via reference.
While it doesn't solve the original problem of passing a reference to the according array element to the callback, the following should be very fast (as noted in the comments, PHP Arrays are implemented using a hashtable).
$items = array(
0=>array('url' => 'url0'),
1=>array('url' => 'url1'),
2=>array('url' => 'url2'),
3=>array('url' => 'url3'),
);
$lookup=array();
foreach($lookup as $k=>$v) {
$lookup[$v['url']]=$k;
}
$curl = new Zebra_cURL();
$curl->get(array_column($urls,'url'),'scan_item',$moreimfo);
function scan_item($result,$moreimfo){
global $lookup,$items;
$items[$lookup[$result->info['url']]]['size'] = strlen($result->body);
}
Probably you may consider using an OOP-approach, with the callback as a method, then the global-izing of the arrays shouldn't be necessary if you use $this->anyMember
I'm trying to do a SOAP call in PHP, it works normally, but i'm with a doubt: How do I can pass arguments to XML creating new nodes according to an array of product's quantity? See this...
That's my XML in SoapUI (with the parts that are important: ITEMSITM > TITEMSITM. The first TITEMSITM is with the fields, the others its the same thing):
<soapenv:Header/>
<soapenv:Body>
<ns:MANUTENCAOSITM>
<ns:SITM>
<ns:CABECALHOSITM>
...
</ns:CABECALHOSITM>
<ns:ITEMSITM>
<!--Zero or more repetitions:-->
<ns:TITEMSITM>
<ns:CODIGOPRODUTO>0000265</ns:CODIGOPRODUTO>
<ns:DESCRICAOPRODUTO>REQ.CAT.0,410 POLI (PL10)</ns:DESCRICAOPRODUTO>
<ns:PERCENTUALDESCONTO>-1.03</ns:PERCENTUALDESCONTO>
<ns:PESOUNITARIO>0.41</ns:PESOUNITARIO>
<ns:PRECOBONIFICADO>10</ns:PRECOBONIFICADO>
<ns:PRECOTABELA>9.700</ns:PRECOTABELA>
<ns:PRECOUNITARIO>9.6</ns:PRECOUNITARIO>
<ns:QUANTIDADEBONIFICADA>20</ns:QUANTIDADEBONIFICADA>
<ns:QUANTIDADEVENDA>200</ns:QUANTIDADEVENDA>
<ns:SALDOBONIFICADO>0</ns:SALDOBONIFICADO>
<ns:TOTALBRUTO>1940.000</ns:TOTALBRUTO>
<ns:TOTALLIQUIDO>1920.000</ns:TOTALLIQUIDO>
<ns:TOTALPESO>82.000</ns:TOTALPESO>
<ns:VALORBONIFICADO>9.700</ns:VALORBONIFICADO>
<ns:VALORLIQUIDO>8.9550</ns:VALORLIQUIDO>
</ns:TITEMSITM>
<ns:TITEMSITM>
...
</ns:TITEMSITM>
<ns:TITEMSITM>
...
</ns:TITEMSITM>
</ns:ITEMSITM>
<ns:RODAPESITM>
<ns:CRESCIMENTOANTERIOR>?</ns:CRESCIMENTOANTERIOR>
<ns:TOTALINVESTIMENTO>0.1303</ns:TOTALINVESTIMENTO>
</ns:RODAPESITM>
</ns:SITM>
<ns:TIPOOPERACAO>3</ns:TIPOOPERACAO>
</ns:MANUTENCAOSITM>
</soapenv:Body>
I need to repeat this node (TITEMSITM) for each product in PHP, but it doesn't work, it just store the last item, like this code below that I try to do, but with no success.
$arguments = array(
'SITM' => array(
'CABECALHOSITM' => $pars1,
'ITEMSITM' => array(
'TITEMSITM' => $parsItem[0],
'TITEMSITM' => $parsItem[1],
'TITEMSITM' => $parsItem[2]
// ...
),
'RODAPESITM' => $pars2
),
'TIPOOPERACAO' => $pars3
);
$inserirItens = $cliente->MANUTENCAOSITM($arguments);
The code above calls with no problems, but when I print_r or var_dump the $arguments, I see that the repetition of the TITEMSITM sends only one product. I think it's simple, but I'm not getting. Can someone help me, please?
References:
PHP SoapClient - Multiple attributes with the same key
SoapClient: how to pass multiple elements with same name?
Here is the code style I used:
$wsdl = 'https://your.api/path?wsdl';
$client = new SoapClient($wsdl);
$multipleSearchValues = [1, 2, 3, 4];
$queryData = ['yourFieldName' => $multipleSearchValues];
$results = $client->YourApiMethod($queryData);
print_r($results);
$ITEMSITM = new stdClass();
foreach ($parsItem as $item) {
$ITEMSITM->TITEMSITM[] = $item;
}
The reasons this should work is because it will closely emulate data structures of your WSDL.