Jul 21 2009

Hacks: Python Calling PHP

Published by tim at 5:57 pm under coding

(This is almost too stupid to post, but on the off chance that someone actually needs something like this…)

I needed to interface with a bunch of data that had PHP wrapper classes, and needed a quick way of being able to interface with PHP from Python. (At this point, you might find it hard to believe that the PHP wrapper classes were worth trying to reuse, but the wrapper classes took care of partitioning and caching and everything, so it seemed silly to not reuse them). I considered using the PHP API and ctypes to make a Python to PHP bridge , but then decided that was entirely too much work, so I just hacked together a stupid class that invoked PHP with arbitrary code and returned it in a number of ways.

Basic usage

Create the PHP class (giving it an optional prefix and postfix — prefix is the most useful, as it allows you to specify requires), and then invoke a code block with get_raw, get, or get_one. Note that every “call” into PHP will create and destroy a PHP process.

Some examples:

Reading raw input

php = PHP("require '../code/private/common.php';")
code = """for ($i = 1; $i <= 10; $i++) { echo "$i\n"; }"""
print php.get_raw(code)

will output a string containing the numbers “1″..”10″

Reading one big JSON value

php = PHP("require '../code/private/common.php';")
code = """
$a = array();
for ($i = 1; $i <= 10; $i++) { $a[] = $i; }
echo json_encode($a);"""
print php.get(code)

This code would return a Python list with the numbers 1..10.

Reading many JSON values

    php = PHP("require '../code/private/common.php';")
    code = """
    for ($i = 1; $i <= 10; $i++) {
        echo json_encode(array($i => $i * $i)) . "\n";
    }"""
    for row in php.get_one(code):
        print row

Given a PHP snippet that returns one JSON value per line, iterate through them as Python values.

The actual code (also on Github):

import popen2
import simplejson as json

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):
        (out, inp) = popen2.popen2("php")
        print >>inp, "<? "
        print >>inp, self.prefix
        print >>inp, code
        print >>inp, self.postfix
        print >>inp, " ?>"
        inp.close()
        return out

    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)

One response so far

One Response to “Hacks: Python Calling PHP”

  1. Drucikon 28 Jul 2009 at 3:47 pm

    Hi!
    As you wrote, someone may needs something like this.
    For me it worked (only warning about popen2 being obsolete) and was very helpful :)

Trackback URI | Comments RSS

Leave a Reply