phpy runs Python and PHP in the same process
Connecting the Python and PHP ecosystems together
At a glance
- What is it?
- phpy is a library for inter-calling Python and PHP, so a PHP application can use Python libraries and a Python application can call PHP packages. It works by creating a Zend VM and a CPython VM side by side in one process and converting values between zval and PyObject, and it does not support Python threading or async IO.
- Who is it for?
- Adopt phpy when you have a PHP application that needs one specific Python library, such as a transformer model or a computer vision tool, and you accept that the call runs in the same process and that Python threading and async IO are unavailable. Skip it if you need to call between the two ecosystems over a network or a process boundary, since phpy is explicitly in-process, or if your workload is concurrent Python.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two runtimes in one address space
The mechanism is the whole point of the project. It creates a ZendVM and a CPython VM in the process at the same time, and directly uses C functions to call each other in the process stack space.
Because both runtimes live in the same process and the calls happen on the same stack, there is no serialisation, no socket and no subprocess. The overhead is only the conversion of zval to PyObject structures and back, which the readme says is why the performance is very high.
That design has an obvious corollary. There is no isolation between the two languages. A fatal error in a Python extension takes down the PHP process, a memory error in PHP corrupts the Python heap, and both runtimes share the same thread. phpy is a way to call across the language boundary cheaply, not a way to keep the languages apart.
The audience is therefore a team with a PHP application that has to reach into the Python ecosystem, or a Python service that has to reuse a PHP package, and who have decided that a process boundary is too expensive or too awkward for the specific call they need.
Calling Python from PHP with PyCore::import
The PHP side is a PHP extension. You compile and install phpy.so as an extension and append extension=phpy.so to php.ini.
The API is centred on import. A module is imported with PyCore::import and the result behaves like an object:
$os = PyCore::import("os");
echo $os->uname();Anything reachable from an imported module is reachable from PHP, which is how the readme demonstrates using a Python machine learning library from PHP. The class is imported from the module and its methods are called directly:
$transformers = PyCore::import('transformers');
$AutoTokenizer = $transformers->AutoTokenizer;
$AutoModelForSequenceClassification = $transformers->AutoModelForSequenceClassification;Note the environment handling in the same example. The proxy setting is read from the PHP environment with getenv and written into the Python process environment through the os module, because the two runtimes do not share an environment. That is a general pattern for this kind of bridge, and it is a good reminder that variables which look global in one language are not global in the other.
The repository's examples directory shows how wide the surface is in practice, with files for a transformer model, a text classification task, a curl binding, a CUDA call, a directory listing, a dictionary, iteration, a class facade, eval and exec.
Calling PHP from Python through a C++ module
The reverse direction is described as importing it as a C++ module. The Python example calls PHP functions by name:
import phpy
content = phpy.call('file_get_contents', 'test.txt')and it can also create a PHP object and call methods on it, which the readme demonstrates with a Redis connection:
o = phpy.Object('redis')
assert o.call('connect', '127.0.0.1', 6379)
rdata = phpy.call('uniqid')
assert o.call('set', 'key', rdata)
assert o.call('get', 'key') == rdataThe two ways of calling are worth distinguishing. phpy.call is a plain function call, so it is the shortest path to a PHP function. phpy.Object constructs a PHP object and returns a handle whose call method dispatches a method on it, which is what you need for a class instance such as a Redis client.
The Python package is built with setuptools. The build backend is declared in pyproject.toml with setuptools and wheel, and setup.py compiles the extension sources, phpy.cc and everything under src/, with the include and library paths taken from the php-config executable. The version is read out of the C header rather than duplicated, by searching php_phpy.h for the PHP_PHPY_VERSION string, so the package version and the extension version cannot drift apart.
The benchmark is about dictionary access, not everything
The readme quotes a specific measurement, and it is worth reading closely rather than paraphrasing loosely. In the benchmark test, a PyDict was created and PHP code and Python code were executed to read and write ten million times respectively. The performance of phpy writing a PyDict with PHP code is 14 percent higher than native Python, and the read performance is 25 percent higher.
That is a narrow claim, and that is exactly what makes it useful as evidence. It is about a hot loop of dictionary reads and writes, which is the workload where conversion overhead would show up first if it were significant. It is not a claim about inference, about network calls, about anything involving the GIL, or about throughput of a whole application.
The readme also points to a more detailed benchmark document under docs/en, so anyone wanting the full picture has somewhere to go.
For a reader deciding whether to adopt phpy, the shape of the benchmark is more informative than the percentages. If your call crosses the boundary once per request, the conversion cost is noise. If it crosses in a tight loop over millions of items, this is the measurement to look at.
No Python threading, no async IO
The limitation is stated as a single bold line in the readme: phpy does not support Python threading or async-io features.
That is the constraint to plan around, and it is a hard one for the kind of workload people usually want a Python library for. A library that does its work in a thread pool, an HTTP client built on asyncio, or anything that expects the event loop to turn, will not behave as it does under CPython on its own. Since the two runtimes share one process and one thread, there is nowhere for a second Python thread to run.
The flip side is that this is not a defect in the bridge so much as a property of the in-process design. The same property that removes serialisation overhead also removes the ability to run two things at once. If your Python side needs concurrency, the correct shape is a separate service reached over a socket, and phpy is the wrong tool for that call.
The other stated platform requirements are milder. phpy supports Linux, Windows and macOS, and it requires PHP 8.1 or later.
Building the extension needs PHP with embed support
Building from source is a C++ extension build, and the setup script makes one demand explicit. It locates the php-config executable, or whatever is pointed at by the PHP_CONFIG environment variable, and calls it to collect the include directories, the prefix and the library directories.
If php-config cannot be run, the script raises a RuntimeError with a message that says to install PHP with embed support, or to set PHP_CONFIG to the matching php-config executable. That is the single most common build failure for this kind of project, because a PHP installation without embed support cannot be used to build an extension that embeds a VM.
The repository supports several build paths. There is config.m4 for the Unix phpize build, config.w32 for the Windows build, a CMakeLists.txt at the top level, a make.sh script, and a Dockerfile. The container image is based on the PHP 8.2.13 CLI image on Debian Bullseye, installs python3 and python3-dev, then extracts the PHP source, runs phpize, configures with the python-config path, makes and installs, and enables the extension:
FROM php:8.2.13-cli-bullseye
LABEL authors="Heelie"
RUN sed -i 's#http://deb.debian.org#https://mirrors.aliyun.com#g' /etc/apt/sources.list
RUN apt update && apt install -y python3 python3-dev
WORKDIR /opt
COPY . /opt/phpy
RUN cd /opt/phpy && docker-php-source extract && \
phpize && \
./configure --with-python-config=/usr/bin/python3-config && \
make && make install && \
docker-php-ext-enable phpy && \
docker-php-source deleteThe example in the repository also shows the same sequence performed by hand inside a container, which is the pattern to copy when you need to build against a specific PHP version.
A repository that shows a lot of real integrations
The directory listing is a catalogue of what people have actually used this for. The examples folder includes a DearPyGui integration, a CEF based one, a chatglm example, a facade example, an import example, a gradio example, a gtk example, an fpm example, and a crash example, alongside plain PHP files for bytes, curl, CUDA, a ddddocr OCR binding, a deepseek binding, dict, dir, eval, exec, fn, iter, itertools product and lib.
That list is more informative than any feature list. It shows a desktop GUI toolkit, an embedded browser, a browser automation binding, a GPU binding and two machine learning models, which tells you the bridge handles objects, callbacks and native extensions rather than only simple values.
The rest of the tree follows the same pattern. The src/ directory holds the C++ sources, include/ and lib/ hold the headers and helper library, stubs/ holds the type stubs for IDEs, tests/ holds the test suites with phpunit.xml and a pytest workflow badge, and docs/ holds the documentation including a benchmark document and an operator mapping table. There is a phpy.json, a composer.json for the PHP side, a package.xml for the PECL channel, and a second Dockerfile, Dockerfile.alpine, for a smaller image.
Version, licence and the pace of releases
phpy is published under the Apache-2.0 licence, with the LICENSE file at the top of the repository, and it is distributed on Packagist for PHP and on PyPI for Python. The readme links a Chinese translation of itself, and the documentation lives under docs/en in the repository rather than on a separate site.
The release history is sparse. The tags are 1.0.11 from 2025-03-05, 1.0.6 from 2024-08-21 and 1.0.5 from 2024-05-31. So the last published version is over a year old at the time of writing, while the last push to the repository was on 2026-10-12 and the repository is not archived.
That gap between release activity and commit activity is the number to watch. The project is clearly still being worked on, with CI running separate PHP and Python test workflows, but a new feature on the main branch is not something you can pick up as a tagged release. Anyone depending on it should pin to a specific commit rather than assume the newest tag contains the recent fixes.
There is also a related tool mentioned in the readme, py2php, an online utility that translates Python code into PHP code. It is a different approach to the same interoperability problem, and it is worth knowing about as an alternative when the call site is a whole algorithm rather than a single function.
Editorial conclusion
Adopt phpy when you have a PHP application that needs one specific Python library, such as a transformer model or a computer vision tool, and you accept that the call runs in the same process and that Python threading and async IO are unavailable. Skip it if you need to call between the two ecosystems over a network or a process boundary, since phpy is explicitly in-process, or if your workload is concurrent Python. Before you build with it, read the limitation that it does not support Python threading or async-io features, check that your PHP is 8.1 or later, and confirm your build has PHP with embed support, which the setup script checks for explicitly.
Frequently asked questions
What is phpy used for?
phpy is a library for inter-calling Python and PHP, so you can use Python functions and libraries in PHP, or use PHP packages in Python. It creates a ZendVM and a CPython VM in the same process and converts values between zval and PyObject structures.
How do I call Python from PHP with phpy?
Compile and install phpy.so as an extension and add extension=phpy.so to php.ini, then import modules with PyCore::import. The readme shows $os = PyCore::import("os") followed by a call such as $os->uname().
How do I call PHP from Python with phpy?
Import it as a C++ module with import phpy, then use phpy.call for plain functions such as phpy.call('file_get_contents', 'test.txt'), or phpy.Object to build a PHP object and call methods on it, for example o.call('connect', '127.0.0.1', 6379).
Does phpy support Python threading or async IO?
No. The readme states that phpy does not support Python threading or async-io features, which is a consequence of running both runtimes in one process and one thread. The package requires PHP 8.1 or later and supports Linux, Windows and macOS.
What does the phpy benchmark measure?
It creates a PyDict and runs PHP code and Python code to read and write ten million times. The readme reports that writing a PyDict from PHP is 14 percent higher than native Python and reading is 25 percent higher, with more detail in docs/en/benchmark.md.
What does building phpy require?
The setup script calls php-config, or the executable named by the PHP_CONFIG environment variable, to collect include and library paths, and it fails with a message asking for PHP with embed support if that executable cannot be run. The build also needs a C++ toolchain and the Python development headers, and the repository ships a Dockerfile that installs python3 and python3-dev before running phpize, configure, make and make install.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/swoole-phpy)Was this useful?