1

I've found that you can run a php file from Python by using this:

import subprocess

proc = subprocess.Popen('php.exe input.php', shell=True, stdout=subprocess.PIPE)
response = proc.stdout.read().decode("utf-8")
print(response)

But is there a way to run php code from a string, not from a file? For example:

<?php
  $a = ['a', 'b', 'c'][0];
  echo($a);
?>
3

2 Answers 2

1

[EDIT]

Use php -r "code" with subprocess.Popen:

def php(code):
    p = subprocess.Popen(["php", "-r", code],
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out = p.communicate() #returns a tuple (stdoutdata, stderrdata)
    if out[1] != b'': raise Exception(out[1].decode('UTF-8'))
    return out[0].decode('UTF-8')

code = """ \
  $a = ['a', 'b', 'c'][2]; \
  echo($a);"""
print(php(code))

[Original Answer]

I found a simple class that allows you to do that.
The code is self-explanatory. The class contains 3 methods:

  • get_raw(self, code): Given a code block, invoke the code and return the raw result as a string
  • get(self, code): Given a code block that emits json, invoke the code and interpret the result as a Python value.
  • get_one(self, code): Given a code block that emits multiple json values (one per line), yield the next value.

The example you wrote would look like this:

php = PHP()
code = """ \
  $a = ['a', 'b', 'c'][0]; \
  echo($a);"""
print (php.get_raw(code))

You can also add a prefix and postfix to the code with PHP(prefix="",postfix"")

PS.: I modified the original class because popen2 is deprecated. I also made the code compatible with Python 3. You can get it here :

import json
import subprocess

class PHP:
    """This class provides a stupid simple interface to PHP code."""

    def __init__(self, prefix="", postfix=""):
        """prefix = optional prefix for all code (usually require statements)
        postfix = optional postfix for all code
        Semicolons are not added automatically, so you'll need to make sure to put them in!"""
        self.prefix = prefix
        self.postfix = postfix

    def __submit(self, code):
        code = self.prefix + code + self.postfix
        p = subprocess.Popen(["php","-r",code], shell=True,
                  stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        (child_stdin, child_stdout) = (p.stdin, p.stdout)
        return child_stdout

    def get_raw(self, code):
        """Given a code block, invoke the code and return the raw result as a string."""
        out = self.__submit(code)
        return out.read()

    def get(self, code):
        """Given a code block that emits json, invoke the code and interpret the result as a Python value."""
        out = self.__submit(code)
        return json.loads(out.read())

    def get_one(self, code):
        """Given a code block that emits multiple json values (one per line), yield the next value."""
        out = self.__submit(code)
        for line in out:
            line = line.strip()
            if line:
                yield json.loads(line)
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks very much for your answer. I found two problems, however, but neither are serious. With close_fds=True included I get the error ValueError: close_fds is not supported on Windows platforms if you redirect stdin/stdout/stderr, but with that removed it runs fine. Then I still get the output in bytes with b at the beginning. So I just added .decode("utf-8") to the print statement, like in my first example. If you make those changes to the answer it will be more helpful.
Also, the functions get and get_one are unneeded for this example, and so could be removed for brevity.
From what I've read: "Note that on Windows, you cannot set close_fds to true and also redirect the standard handles by setting stdin, stdout or stderr." I realized that it was working for me because I am using python 3.7: "Changed in version 3.7: It’s now possible to set close_fds to True when redirecting the standard handles"
Is there any advantage of using close_fds?
I think it's better to leave out close_fds and let the subprocess decide what is best according to your OS and python version. Also, reading the docs I could note that it recommends using shell=False to avoid code injection and using the method communicate() instead of read() when you use stdout=PIPE. stdin is also not necessary if you don't need to send a streaming input. I've edited my post to include a much simpler version. I've added exception handling though, which was made easier with communicate()
1

Based on Victor Val's answer, here is my own compact version.

import subprocess

def run(code):
    p = subprocess.Popen(['php','-r',code], stdout=subprocess.PIPE)
    return p.stdout.read().decode('utf-8')

code = """ \
  $a = ['a', 'b', 'c'][0]; \
  echo($a);"""
print(run(code))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.