0

I have a terminal application that is currently written in Bash, about 20,000 SLOC. One thing that I like about the way that it's set up is that it is quite modular. Different components are spread across different files, which can be executed at run time. This means that parts of the system can be "hot swapped" or updated during runtime without needing to kill the main program and re-execute it.

The disadvantage is there is a lot of database I/O, which makes it super janky since Bash is really not suited for this. Currently, it interfaces with HTTP APIs which spawn PHP.

I'd like to rewrite it natively in PHP (CLI) to cut out the middleman layer, so the application can communicate directly with the database, making maintenance much easier. One problem I've been pondering though is how to replicate the same modularity with Bash. With Bash, if I call script A, and I make a change to script B, and then I enter script B from script A (assuming it's in a conditional block somewhere, not right at the top of the file), the changes are picked up without needing to re-execute script A, since script B isn't interpreted until it gets executed.

I'm struggling to figure out how to achieve this with PHP. For instance, this will not work:

include('script.php');

The reason is that includes are all executed when the script is interpreted, not when it is executed at run time.

A similar question has been asked already, but it doesn't specifically address this aspect, just how to launch another script in general. I want to basically be able to spawn the script anew at runtime, when the application decides it should be executed. shell_exec and passthru seem to be all that is built into PHP that would be similar, but I'm not sure this is right since that's just spawning another system shell and doing it there, so it's not as "direct" as with Bash.

What would be the proper equivalent in PHP of this in Bash:

if [ "$x" = "3" ]
then
   ./launchscriptnow.sh
fi

The user should now be executing launchscriptnow.sh. Remember that this is an interactive application, so it's not just doing something and returning a value. The user could be here for 2 seconds, 5 minutes, or an hour.

So that ./launchscriptnow.sh is only interpreted when the code gets to that line, not when the parent script itself is interpreted? Is this kind of thing purely a shell construct or is there an equivalent to this?

InterLinked
  • 1,247
  • 2
  • 18
  • 50
  • Well, you could call the PHP-Script over CLI by using exec ('php YOUR-SCRIPT.php') or something like that. – wayneOS Jun 28 '21 at 13:06
  • @wayneOS Sorry, I've clarified my question a little bit. It needs to be interactive which I don't think exec fulfills – InterLinked Jun 28 '21 at 13:14
  • 1
    Instead of `exec`, could you use [`proc_open`](https://www.php.net/manual/en/function.proc-open.php) and then do what wayneOS says? – Chris Haas Jun 28 '21 at 13:39
  • 1
    Try [`passthru`](https://www.php.net/manual/en/function.passthru.php). It's effectively `exec` but with stdin and stdout attached as you'd expect. – msbit Jun 28 '21 at 13:56
  • @msbit Unfortunately, `passthru` seems to be not be up to it, I get `Fatal error: Uncaught PhpSchool\CliMenu\Exception\InvalidTerminalException: Terminal is not interactive (TTY)`. If I run the PHP script directly from the shell, this doesn't happen, so passthru seems to be limited or filtering stuff out – InterLinked Jul 28 '21 at 14:55

2 Answers2

2

I'm don't understand your concern about "when the script is interpreted" vs "when the script is executed"? I have a number of scripts that use a variable name for the script to be included and make that decision right before executing the include statement. $result = include($script_name) will work fine and the decision about which script to include can be made at run time.

The way you describe the problem does not seem to indicate that you want to "launch" another process, but I could be wrong.

nusbaum
  • 36
  • 5
  • In Bash, doing something like `./launchscriptnow.sh` from within a script executes a new Bash instance as a subshell, allowing hotloading, which is not how PHP `include`/`require` work (they load the file once only). – msbit Jun 28 '21 at 14:05
  • msbit has it right, I need to hot load rather than load when the parent script is loaded. – InterLinked Jun 28 '21 at 14:12
  • `include_once($file_name)` includes a file once. `include($file_name)' will load the file again and can indeed be used for hot loading. – nusbaum Jun 28 '21 at 14:12
  • include($file_name) will "load" the script when the function is called, not when the parent script is loaded. It is not a 'C' style include. – nusbaum Jun 28 '21 at 14:15
  • Huh, very interesting! I just tried this out, simply calling `require`/`include` in a loop, and you are right @nusbaum, the required/included file is re-evaluated each time. This goes against how I thought the PHP opcode cache must work; I guess you learn something new every day. – msbit Jun 28 '21 at 14:55
  • I have an old MVC framework that makes a runtime decision about which "controller" is needed and then uses an `include($controller_name)` to actually run that controller. That seems similar to what was being described here. – nusbaum Jun 28 '21 at 15:18
  • Oh, yes I understand the idea of optionally invoking `include`/`require` based on conditions. What surprised me was that calling `include`/`require` multiple times in a single program lifetime (e.g. `while (true) { include 'child.php'; }`) would actually load that file from disk anew and evaluate it multiple times. – msbit Jun 29 '21 at 06:04
1

Against all recommendations you could:

$script = file_get_contents('module_b.php');
$script = str_replace('<' . '?php', '', $script);
$script = str_replace('?' . '>', '', $script);
eval($script);
Teson
  • 6,644
  • 8
  • 46
  • 69
  • 1
    After a bit of investigation, I'd suggest that @nusbaum's answer would be better for your use-case. – msbit Jun 28 '21 at 15:00