1
2
My program is:"process.stdout.write(eval('1+1;').toString())"
Output:2
Let's examine these parts. They are simple but important:
eval(myprogram)
toString()
process.stdout.write()
This is called a runner. This runner has many limitations. Let's address them one by one:
Error handling
Discuss
What happens if we change $code to instead of being
"1 + 1"
something like
"1 + i_am_not_defined"
?
Run your program with
node -e "<program>"
in the command line, to see what should happen.
Let's add error handling to our little program:
1
2
3
4
echo('My program is: '.$embedded.'<br/>');echo('Exit code: '.$process->getExitCode().'<br/>');echo('Output: '.$process->getOutput().'<br/>');echo('Error Output: '.$process->getErrorOutput().'<br/>');
1
2
3
4
My program is:"process.stdout.write(eval('1+i_am_not_defined;').toString())"
Exit code:1
Output:
Error Output: undefined:11+i_am_not_defined;^ ReferenceError: i_am_not_defined is not defined at eval(eval at([eval]:1:22),:1:3) at [eval]:1:22 at Script.runInThisContext(vm.js:91:20) at Object.runInThisContext(vm.js:298:38) at Object.([eval]-wrapper:6:22) at Module._compile(internal/modules/cjs/loader.js:689:30) at evalScript(internal/bootstrap/node.js:563:27) at startup(internal/bootstrap/node.js:248:9) at bootstrapNodeJSCore(internal/bootstrap/node.js:596:3)
There is much more we could do here, but at least this will give us some feedback to move on.
Multiline support
Let's try another silly problem. Can we run this code?
1
2
3
4
5
6
$code=<<<JS
var sum = function(a, b) {
return a + b;
}
sum(1, 1);
JS;
Exercise
Run this code.
The problem is that we are writing a multiline program and running it with node -e. We will also experience problems when our program is big enough to not fit the command line restrictions (a limitation that varies depending on the OS).
We could for instance write a temporary file.
Let's try this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?phpnamespaceApp;require__DIR__.'/vendor/autoload.php';useSymfony\Component\Process\Process;functioncreateTemporaryFile($content=null,$extension=null){$dir=rtrim(sys_get_temp_dir(),DIRECTORY_SEPARATOR);if(!is_dir($dir)){if(false=== @mkdir($dir,0777,true)&&!is_dir($dir)){thrownew\RuntimeException(sprintf("Unable to create directory: %s\n",$dir));}}elseif(!is_writable($dir)){thrownew\RuntimeException(sprintf("Unable to write in directory: %s\n",$dir));}$filename=$dir.DIRECTORY_SEPARATOR.uniqid('ssr_',true);if(null!==$extension){$filename.='.'.$extension;}if(null!==$content){file_put_contents($filename,$content);}return$filename;}$code=<<<JS
var sum = function(a, b) {
return a + b;
}
sum(1, 1);
JS;$embedded='process.stdout.write(eval('.json_encode($code).').toString())';$filename=createTemporaryFile($embedded,'js');$process=newProcess('node '.$filename);$process->run();echo('<html><body>');echo('My program is: '.$embedded.'<br/>');echo('Filename : '.$filename.'<br/>');echo('Exit code: '.$process->getExitCode().'<br/>');echo('Output: '.$process->getOutput().'<br/>');echo('Error Output: '.$process->getErrorOutput().'<br/>');echo('</body></html>');
Apart from creating a temporary file (that we should take care to remove in a real environment), this line has changed
Typically we will face this situation. We will want to have two pieces of code:
A (rather long) program that doesn't change between requests, or context code.
A (rather short) program that changes between requests, or variant code.
Let's express it. Change this part:
1
2
3
4
5
6
$code = <<<JS
var sum = function(a, b) {
return a + b;
}
sum(1, 1);
JS;
To this:
1
2
3
4
5
6
7
8
9
10
11
$context = <<<JS
var sum = function(a, b) {
return a + b;
};
JS;
$variant = <<<JS
sum(1, 1);
JS;
$code = $context . $variant;
This expresses what we will want to do. We will have a (rather big) application, and in each request we will want to say "hey, app, render this component with these parameters".