Reputation: 1801
Here is my PHP code:
<?php
function getItems($arg) {
$products = array();
$products[] = (object) array('id' => '0', 'title' => 'Product1');
$products[] = (object) array('id' => '1', 'title' => 'Product2');
return $products;
}
$jsonContent = '{"myKey":["loop", "getItems(\"Arg\")", "<div><h3>{$item->title}</h3></div>"]}';
$jsonObj = json_decode($jsonContent);
$options = $jsonObj->myKey;
if($options[0] == 'loop') {
$html = [];
foreach($options[1] as $item) {
$html[] = $options[2];
}
echo implode('', $html);
}
?>
Of course, I get the warning
Invalid argument supplied for foreach() ...
because the argument for the foreach()
is not an array but a string.
So how to convert a string as name of a function and call the function?
And how to use the third element of the $options
array as a wrapper with dynamic data from the loop (I hope you will understand me by the code)
I need to get the following result:
<div><h3>Product1</h3></div>
<div><h3>Product2</h3></div>
Upvotes: 0
Views: 542
Reputation: 79024
This is hackish and uses eval
which can be dangerous on untrusted data, but for fun:
$arg = 'something';
if($options[0] == 'loop') {
$func = rtrim($options[1], '()');
foreach($func($arg) as $item) {
eval("echo \"{$options[2]}\";");
}
}
Or a slightly different approach without an argument:
if($options[0] == 'loop') {
eval("\$items = {$options[1]};");
foreach($items as $item) {
eval("echo \"{$options[2]}\";");
}
}
Upvotes: 0
Reputation: 23968
The basic idea of it is to skip the foreach and just "run it" and input options[2] as argument, however since the string has ()
this had to be removed with substr.
$html = [];
$html[] =substr($options[1],0,-2)($options[2]);
var_dump($html);
I see you asked of a different approach without the eval.
You can use str_replace in a loop in your function.
Because products are undefined when the string is created then the "" will not concert it to products value in the function.
But this will (I assume) give you the output you want.
function getItems($item) {
$products = array();
$products[] = (object) array('id' => '0', 'title' => 'Product1');
$products[] = (object) array('id' => '1', 'title' => 'Product2');
$return = "";
foreach($products as $prod){
$return .= str_replace('$products->title',$prod->title, $item);
}
return $return;
}
$jsonContent = '{"myKey":["loop", "getItems()", "<div><h3>{$products->title}</h3></div>", "<div><h1>{$products->title}</h1></div>"]}';
$jsonObj = json_decode($jsonContent);
$options = $jsonObj->myKey;
if($options[0] == 'loop') {
$html = [];
$func = substr($options[1],0,-2);
foreach(array_slice($options,2) as $arg){
$html[] = $func($arg);
}
var_dump($html);
}
/*
array(2) {
[0]=>
string(60) "<div><h3>{Product1}</h3></div><div><h3>{Product2}</h3></div>"
[1]=>
string(60) "<div><h1>{Product1}</h1></div><div><h1>{Product2}</h1></div>"
}
*/
If you can't change the code in function then do the same processing in the "front".
function getItems() {
$products = array();
$products[] = (object) array('id' => '0', 'title' => 'Product1');
$products[] = (object) array('id' => '1', 'title' => 'Product2');
return $products;
}
$jsonContent = '{"myKey":["loop", "getItems()", "<div><h3>{$products->title}</h3></div>", "<div><h1>{$products->title}</h1></div>"]}';
$jsonObj = json_decode($jsonContent);
$options = $jsonObj->myKey;
if($options[0] == 'loop') {
$html = [];
$func = substr($options[1],0,-2);
$products = $func();
foreach(array_slice($options,2) as $arg){
foreach($products as $prod){
$html[] = str_replace('$products->title',$prod->title, $arg);
}
}
var_dump($html);
}
/*
array(4) {
[0]=>
string(30) "<div><h3>{Product1}</h3></div>"
[1]=>
string(30) "<div><h3>{Product2}</h3></div>"
[2]=>
string(30) "<div><h1>{Product1}</h1></div>"
[3]=>
string(30) "<div><h1>{Product2}</h1></div>"
}
*/
Upvotes: 1
Reputation: 41820
I think this would be more workable with a couple of changes to the JSON.
First, I think it would make the code more readable if options
was an object rather than an array, but this same approach should still work with an array if you prefer that. Second, I think the function arguments and any object properties used in the template should be separate options to avoid use of eval()
.
{
"myKey": {
"type": "loop",
"function": "getItems",
"args": [
"Arg"
],
"template": "<div><h3>%s<\/h3><\/div>",
"properties": [
"title"
]
}
}
The code for processing options after decoding the JSON:
if ($options->type == 'loop') {
$html = [];
foreach (($options->function)(...$options->args) as $item) {
$html[] = vsprintf($options->template, array_map(function ($property) use ($item) {
return $item->$property;
}, $options->properties));
}
echo implode('', $html);
}
I would not recommend using this if the JSON would ever come from any external source. It seems like a good way to let people execute arbitrary code on your system, even without eval
. Even if you're only using it yourself, I think it would still be prudent to at least validate the function name against a list of allowed functions.
Code for options array rather than object:
JSON:
{"myKey":["loop","getItems",["Arg"],"<div><h3>%s<\/h3><\/div>",["title"]]}
PHP:
if ($options[0] == 'loop') {
$html = [];
foreach (($options[1])(...$options[2]) as $item) {
$html[] = vsprintf($options[3], array_map(function ($property) use ($item) {
return $item->$property;
}, $options[4]));
}
echo implode('', $html);
}
Upvotes: 0
Reputation: 57141
With the extra complication of adding in the value to the HTML, I can only think of the evil eval
. But for the rest, just separate the function name from the parameters and create them as an array in the JSON. These can then be passed in using the Argument unpacking ...
operator. I've amended the function to show how the passed in arguments could work.
In the JSON I have
["Product", 3]
which maps onto
function getItems( $prefix, $start) {
So to put this together...
function getItems( $prefix, $start) {
$products = array();
$products[] = (object) array('id' => '0', 'title' => $prefix.$start);
$products[] = (object) array('id' => '1', 'title' => $prefix.($start+1));
return $products;
}
$jsonContent = '{"myKey":["loop", "getItems", ["Product", 3],
"<div><h3>{$item->title}</h3></div>"]}';
$jsonObj = json_decode($jsonContent);
$options = $jsonObj->myKey;
if($options[0] == 'loop') {
$html = [];
foreach($options[1](...$options[2]) as $item) {
$html[] = eval("return \"{$options[3]}\";");
}
echo implode('', $html);
}
which returns
<div><h3>Product3</h3></div><div><h3>Product4</h3></div>
Upvotes: 1
Reputation: 136
Use call_user_func to call a function from a string, and as pointed out by Andreas, use separate array positions for function and argument(s)
But I wanna point out that this is UTTERLY complicated and maybe not necessary... are there any other ways you can uncomplicate it ?
EDIT:
Why do you need to pass an arg that you don't even use anyway ?
function getItems($arg) {
$products = array();
$products[] = (object) array('id' => '0', 'title' => 'Product1');
$products[] = (object) array('id' => '1', 'title' => 'Product2');
return $products;
}
$jsonContent = '{"myKey":["loop", "getItems", "Arg1,Arg2", "<div><h3>{$item->title}</h3></div>"]}';
$jsonObj = json_decode($jsonContent);
$options = $jsonObj->myKey;
if($options[0] == 'loop') {
$html = [];
$args = explode(',', $options[2]);
foreach($args as $item) {
$html[] = call_user_func ($options[1], $item);
}
echo implode('', $html);
}
?>````
Upvotes: 0
Reputation: 104
In order to call a string as your function, you need to remove the ()
from your string.
$jsonContent = '{"myKey":["loop", "getItems", "<div><h3>{$item->title}</h3></div>"]}';
Then, on your foreach()
loop, add them again after $options[0]
like this:
foreach( $options[1]() as $item) {
$html[] = $options[2];
}
Upvotes: 0