I have some code that looks for a branch of a JSON string called applications and then for each item it finds, it executes some code.
This works fine if the JSON contains "applications" but falls over if it doesn't.
My PHP
$json = file_get_contents("programs.json");
$array = json_decode($json, true);
foreach ($array['applications'] as $app) {
$application = $app['application'];
// Do some stuff
}
My JSON looks like this:
{
"hostname": "MINIPC025",
"applications": [{
"application": "7-Zip 19.00 (x64)"
},
{
"application": "Active Directory Rights Management Services Client 2.1"
},
{
"application": "Adobe Acrobat Reader DC MUI"
}
]
}
So my question is - is it possible to put some kind of if statement that says only loop through the applications bit of the JSON data if the applications property exists.
Thank you.
There are many ways to Rome, as they say. A simple if, where you can use a variety of native PHP functions, but also the usage of the null-coalescing operator ??.
They all end up with the same idea -- just iterate over $array['applications'] if it exists. None of these checks will actually check if its an iterable variable though, so if $array['applications'] = 'myString';, you will still receive errors. You can use the native PHP function is_iterable() to check that.
Using null-coalescing operator ??
foreach ($array['applications'] ?? [] as $app) {
// ...
}
Using array_key_exists()
if (array_key_exists('applications', $array)) {
foreach ($array['applications'] as $app) {
// ...
}
}
Using isset()
if (isset($array['applications'])) {
foreach ($array['applications'] as $app) {
// ...
}
}
Using !empty()
if (!empty($array['applications'])) {
foreach ($array['applications'] as $app) {
// ...
}
}
Of the above here, I'd recommend using either isset()/!empty() or the null-coalescing operator, as array_key_exists() will still return true for the case of $array['applications'] = null;.
Some useful, related reading
What's the difference between isset() and array_key_exists()?
isset() and empty() - what to use
You could use isset to check if a variable is declared and is different than NULL
if(isset($array['applications'])){
foreach ($array['applications'] as $app)
...
}
You can use
property_exists($json, 'applications')
$json = file_get_contents("programs.json");
$array = json_decode($json, true);
if (property_exists($array, 'applications')) {
foreach ($array['applications'] as $app) {
$application = $app['application'];
// Do some stuff
}
}
Works only if json is object.
Related
So, after doing some studying I have successfully managed to parse some XML that I'm getting via Guzzle via simplexml_load_string. The issue is then when I then subsequently try to dispatch a job for each of the children using the following code I get a "Serialization of 'SimpleXMLElement' is not allowed" error.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
So to try and fix this I can use the following trick to convert the XML into an array;
json_decode(json_encode($child))
however, while this does mean I can send the data to the new job, it does mean that, as far as I can work out, I have no way to access the #attributes. An alternative would be something along the lines of the following;
// ParentJob
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML, true), $this->otherVar);
}
// ChildJob
public function __construct($xml, $otherVar)
{
$this->xml = simplexml_load_string($xml);
$this->otherVar = $otherVar;
}
however it still throws a serialization error on the dispatch for some reason that I cannot work out, since it sould only be sending raw XML and not an object.
So my main question is what would be the correct way to pass and child SimpleXMLObject to a job in Laravel 5.3 ?
(short of something like looping through all the nodes/attributes and building my own collection from them)
Converting the XML into JSON that way means loosing data. I suggest keeping the XML if possible.
SimpleXMLElement::asXML() is a method. Do not forget the brackets.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML(), true), $this->otherVar);
}
Calling it as a property means that SimpleXML tries to interpret it as a child element node. This means it will be an (empty) SimpleXMLElement.
Here is a small example showing the behavior:
$node = new SimpleXMLElement('<foo/>');
var_dump($node->asXml);
var_dump($node->asXml->getName());
var_dump($node->asXml());
Output:
object(SimpleXMLElement)#2 (0) {
}
string(0) ""
string(29) "<?xml version="1.0"?>
<foo/>
"
The SimpleXmlElement can be converted to array as follows:
$xml = <<<'XML'
<root>
<x a="a1">1</x>
<y b="b2">2</y>
<z>3</z>
</root>
XML;
$xe = simplexml_load_string($xml);
$a = $xe->xpath('*');
$a = array_map(function ($e) {
$item = (array) $e;
$item['nodeName'] = $e->getName();
return $item;
}, $a);
// Now `$a` is an array (serializable object)
echo json_encode($a, JSON_PRETTY_PRINT);
Output
[
{
"#attributes": {
"a": "a1"
},
"0": "1",
"nodeName": "x"
},
{
"#attributes": {
"b": "b2"
},
"0": "2",
"nodeName": "y"
},
{
"0": "3",
"nodeName": "z"
}
]
Note, you can get the string value of a SimpleXmlElement by casting it to string:
$item['value'] = (string) $e;
Since xpath method supports relative XPath expressions, the asterisk should work even with namespaced XMLs. Consider using the DOM extension, as it is much more flexible than SimpleXML. In particular, its DOMXPath class allows to register namespaces and use the registered identifiers in the XPath expressions:
$xpath->registerNamespace('myprefix', 'http://example.com/ns');
$xpath->query('/root/myprefix:*');
As it turns out the entire reason it was not working was due to me using simplexml_load_string() on the child jobs constructor, which was turning it into a simpleXMLElement before the job was actually serialized and pushed onto the queue. the correct way to do it was to parse the XML string on the handle method, which is done after the job has been pulled from the queue for actual processing.
Now this works I can simply dispatch the child job with $child->asXML, and parse it when the job is actually being processed, meaning I can still use all the nifty simpleXML features such as attributes().
Example ParentJob:
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
Example ChildJob:
protected $xml;
protected $otherVar;
public function __construct($xml, $otherVar)
{
$this->xml = $xml;
$this->otherVar = $otherVar;
}
public function handle()
{
$child = simplexml_load_string($this->xml);
$attributes = $child->attributes();
}
I am looking to create an extension api for my web application.
Example extension file:
function echoCustomHeaders(){
echo '<li>Header Link</li>';
}
There would be several files similar to the example extension file (with the same function name, for user friendlyness when programming addons).
for($x=0;$x<count($extension_files);$x++){
//This would obviosely break when it gets to the second file, as functions cannot be declared twice in php
require_once($extension_files[$x]);
}
//some code later...
//this should call echoCustomHeaders() in ALL of the extension files, what code should I put here to make this happen?
echoCustomHeaders();
In case you are wondering about what the question is, read the comments in the code above and it should be fairly easy to see.
Return closures (lambda expressions) in your extension files as follows:
return function(){
echo '<li>Header Link</li>';
}
In PHP the include/require statement is really a function and therefore has a return value, hence you can collect those closures into an array:
$closures = array();
for($x=0;$x<count($extension_files);$x++){
$closures[$i]=include($extension_files[$x]);
}
// Do whatever you want with your closures, e.g. execute them:
foreach($closures as $closure) {
$closure();
}
ADDED CONTENT:
In the case if you would like to return multiple closures with each include, you may return an array of closures, indexed by the name of them:
return array(
'echoCustomHeaders' => function() {
echo '<li>Header Link</li>';
},
// ...
);
Then you can still execute some of them by their name:
$closureArray = array();
foreach($extension_files as $file) {
$closureArray[] = include($file);
}
foreach($closureArray as $closure) {
if(isset($closure['echoCustomHeaders'])) // Maybe there wasn't an echoCustomHeaders in each extension file ...
$closure['echoCustomHeaders']();
}
Maybe it would be a better idea to even separate the different kind of extension functions into distinct arrays:
$closureArray = array();
foreach($extension_files as $file) {
$functions = include($file);
foreach($functions as $name => $function) {
if(!isset($closureArray[$name]))
$closureArray[$name] = array();
$closureArray[$name][] = $function;
}
}
foreach($closureArray['echoCustomHeaders'] as $closure) {
$closure();
}
Another solution is to use a more object oriented way, and declare a new extension class in each extension file. However, if there would be no data sharing required between the extension methods in an extension file, then simply returning the functions as an array of closures is a more lightweight and cleaner solution in my opinion.
1.maybe you can use the new feature after php5.3:namespace http://www.php.net/manual/en/language.namespaces.php, then you can use the same name functions.
2.however you could think about the object oriented solution,for example,defined a base class who has a method echoCustomHeaders.
I'm scratching my head at some PHP code. I can't tell if it logically works yet, as I'm quite new to PHP and it won't execute yet due to a syntax error: syntax error, unexpected 'use' (T_USE), expecting '{'
The syntax error is tripping up on a use, despite the PHP version fully supporting it, and the syntax (as far as I can tell) being correct.
The code checks which database implementation is specified, and it'll define the functions for whichever one it is. Because I can't define functions returned from other functions as global functions, I store it in them in variables, 'wrapping' access via global functions that capture the variables from their outer scopes, or at least they should do.
The two lines yielding errors have been marked:-
function query_escaper($element_escaper) {
return function($query, $values) use ($element_escaper) {
return join(array_map(
function($query_elem, $value) {
return isset($value)
? $query_elem . $element_escaper($value)
: $query_elem
;
},
explode('?', $query),
$values
));
};
}
function define_postgresql_functions() {
[...]
$escape_query = query_escaper('pg_escape_literal');
#
# XXX The error occurs here :-
#
function escape_query() use ($escape_query) {
return call_user_func_array($escape_query, func_get_args());
}
[...]
}
function define_mysql_functions() {
[...]
$escape_query = query_escaper(function($str) {
return use_database(function($database) use ($str) {
return $database->real_escape_string($str);
});
});
#
# XXX The same error also occurs here :-
#
function escape_query() use ($escape_query) {
return call_user_func_array($escape_query, func_get_args());
}
[...]
}
switch ($database_info['type']) {
case 'postgresql':
define_postgresql_functions();
break;
case 'mysql':
define_mysql_functions();
break;
default:
throw new DatabaseNotImplementedException();
}
I'm looking for two possible solutions: either being able to capture those variables from the outer scope, or another way of defining global functions as functions returned from other functions.
The pattern function\s+\w+ always declares a function in the global scope, meaning you have to use the global syntax. This means you can't name the function if you're creating a closure. So this is invalid:
function escape_query() use ($escape_query) {
But this would work fine:
$escape_function = function() use ($escape_query) {
There are a few different ways to accomplish what you want. eval comes to mind, as does using a static variable in the function, or the OOP solution.
I think the OOP solution is probably the easiest:
class Escaper {
public static $escape_function;
}
// in your function
Escaper::$escape_function = function() use ($escape_query) { //...
// later
Escaper::$escape_function('a','b','c');
I am using PHP 5.3.6 from MAMP.
I have a use case where it would be best to use PHP's Iterator interface methods, next(), current(), and valid() to iterate through a collection. A foreach loop will NOT work for me in my particular situation. A simplified while loop might look like
<?php
while ($iter->valid()) {
// do something with $iter->current()
$iter->next();
}
Should the above code always work when $iter implements PHP's Iterator interface? How does PHP's foreach keyword deal with Iterators?
The reason I ask is that the code I am writing may be given an ArrayIterator or a MongoCursor. Both implement PHP's Iterator interface but they behave differently. I would like to know if there is a bug in PHP or PHP's Mongo extension.
ArrayIterator::valid() returns true before any call to next() -- immediately after the ArrayIterator is created.
MongoCursor::valid() only returns true after the first call to next(). Therefore the while loop above will never execute.
At risk of being verbose, the following code demonstrates these assertions:
<?php
// Set up array iterator
$arr = array("first");
$iter = new \ArrayIterator($arr);
// Test array iterator
echo(($iter->valid() ? "true" : "false")."\n"); // Echoes true
var_dump($iter->current()."\n"); // "first"
$iter->next();
echo(($iter->valid() ? "true" : "false")."\n"); // Echoes false
// Set up mongo iterator
$m = new \Mongo();
$collection = $m->selectDB("iterTest")->selectCollection("mystuff");
$collection->drop(); // Ensure collection is empty
$collection->insert(array('a' => 'b'));
$miter = $collection->find(); // should find one object
// Test mongo iterator
echo(($miter->valid() ? "true" : "false")."\n"); // Echoes false
$miter->next();
echo(($miter->valid() ? "true" : "false")."\n"); // Echoes true
var_dump($miter->current()); // Array(...)
Which implementation is correct? I found little documentation to support either behavior, and the official PHP documentation is either ambiguous or I'm reading it wrong. The doc for Iterator::valid() states:
This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid.
This would suggest that my while loop should first call next().
Yet the PHP documentation for Iterator::next states:
This method is called after each foreach loop.
This would suggest that my while loop is correct as written.
To summarize - how should PHP iterators behave?
This is an interesting question. I'm not sure why a foreach won't work for you, but I have some ideas.
Take a look at the example given on the Iterator interface reference page. It shows the order in which PHP's internal implementation of foreach calls the Iterator methods. In particular, notice that when the foreach is first set up, the very first call is to rewind(). This example, though it's not well-annotated, is the basis for my answer.
I'm not sure why a MongoCursor would not return true for valid() until after next() is called, but you should be able to reset either type of object by calling rewind() prior to your loop. So you would have:
// $iter may be either MongoCursor or ArrayIterator
$iter->rewind();
while( $iter->valid() ){
// do something with $iter->current()
$iter->next();
}
I believe this should work for you. If it does not, the Mongo class may have a bug in it.
Edit: Mike Purcell's answer correctly calls out that ArrayIterator and Iterator are not the same. However, ArrayIterator implements Iterator, so you should be able to use rewind() as I show above on either of them.
Subclassing any Iterator and echo'ing when it's called will tell you how it behaves.
Example (demo)
class MyArrayIterator extends ArrayIterator
{
public function __construct ($array)
{
echo __METHOD__, PHP_EOL;
parent::__construct($array);
}
…
}
foreach (new MyArrayIterator(range(1,3)) as $k => $v) {
echo "$k => $v", PHP_EOL;
}
Output
MyArrayIterator::__construct
MyArrayIterator::rewind
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
0 => 1
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
1 => 2
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
2 => 3
MyArrayIterator::next
MyArrayIterator::valid
This is equivalent to doing
$iterator = new MyArrayIterator(range(1,3));
for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
echo "{$iterator->key()} => {$iterator->current()}", PHP_EOL;
}
The sequence in which the methods are called is identical to a custom Iterator:
class MyIterator implements Iterator
{
protected $iterations = 0;
public function current()
{
echo __METHOD__, PHP_EOL;
return $this->iterations;
}
public function key ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations;
}
public function next ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations++;
}
public function rewind ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations = 0;
}
public function valid ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations < 3;
}
}
foreach (new MyIterator as $k => $v) {
echo "$k => $v", PHP_EOL;
}
It seems you may have mixed up Iterator and ArrayIterator, as each has their own valid() api call.
Array Iterator : There is no specific mention to next() etc, but the example clearly demonstrates what you mentioned in your OP, to paraphrase; that no other AI api call needs to be made to determine if current element of array is valid.
Iterator : As you mention: "This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid."
As such, I would stick with ArrayIterator, as it demonstrates the most correct behavior, in that valid() will correctly determine if current element in array is valid, without having to make another api call (next, rewind).
If you want get Mongo to behave like AI does, you could add an instance check before starting the while loop:
if ($iter instanceof MongoCursor) {
$iter->next()
}
while ($iter->valid()) {
// Do stuff
}
I have a PHP foreach from an array, the array is given to me by my DB provider via a soap web service so I cannot change the array I get. When there are no elements to return, I get an empty array, this results in
Warning: Invalid argument supplied for foreach()
the loop looks like
foreach (($point1['return']) as $val)
Where can I put an # to stop this warning, and if I cant, what I do I do to turn off php warnings.
Hiding the warning is not the right way. You should check whether it exists and is an array.
if (is_array($point1['return'])) {
foreach ($point1['return'] as $val) {
...
}
}
PHP is_array()
Actually, turning off warnings or using the # operator is not the right way to go 99% of the time.
Solve the problem instead of hiding it.
foreach() can handle not only arrays but also objects by either using the the default "all visible properties" implementation or a custom implementation via the traversable/iterator interface.
And a "DB provider via a soap web service" is something where I'd keep an eye on the possibility of (suddenly) having an object/iterator instead of a plain array.
So, if you're going to test the existence and data type before passing the variable to foreach, you should consider not only testing for is_array() but also for instanceof Traversable.
<?php
class Foo implements Iterator {
protected $v = 0;
public function current() { return $this->v; }
public function key() { return $this->v; }
public function next() { ++$this->v; }
public function rewind() { $this->v=0; }
public function valid() { return 10>$this->v; }
}
//$a = array(1,2,3,4);
$a = new Foo;
if( is_array($a) || $a instanceof Traversable ) {
foreach($a as $e) {
echo $e, "\n";
}
}
An empty array does not cause that error, the problem is that you are trying to iterate trough something that is not an array. You could add a check using is_array function.
Better to let errors display but check that the input is an array first. So you could wrap the foreach in an if, like this:
if ((is_array($point1)) && (is_array($point1['return']))) {
foreach (($point1['return']) as $val)
...
}
Check first for an array:
if(is_array($point1['return']))
{
...
}
You can also explicitly cast the argument to array:
foreach ((array) $point1['return'] as $val) {
Note: this still will issue undefined index, if there is no 'return' key in $point1
Check whether that is actually an array. with is_array(); !!
There's no need to suppress the warning.
As a matter of fact, It's not possible to suppress that invalid argument warning.
Paste this into your function file:
set_error_handler(function($errno, $errstr){
if(stristr($errstr,'Invalid argument supplied for foreach()')){
return true;
}
return false;
});