There are many ways we can interact with other applications from PHP and share data; there’s web services, message queuing systems, sockets, temporary files,
exec(), etc. Well, today I’d like to show you one approach in particular,
proc_open(). The function spawns a new command but with open file pointers which can be used to send and receive data to achieve interprocess communication (IPC).
What’s a Pipe?To understand how
proc_open() and the process sends and receives data, you know know what a pipe is.
The Unix Philosophy lead early developers to write many small programs each with very specific functionality. These programs shared plain text as a common format, and users could “chain them together” for greater functionality. The output from one command become the input for the next. The virtual channels that let the data flow between commands became known as pipes.
If you’ve ever worked in a Unix shell then there’s a good chance you’ve used pipes, perhaps even without realizing it. For example:
$ mysql -u dbuser -p test < mydata.sqlHere the
mysql utility is invoked and a pipe is set up by the system through which the contents of the
mydata.sql file is fed into
mysql as if you were typing directly at it’s prompt from your keyboard.
There are two types of pipes: anonymous and named. An anonymous pipe is ad hoc, created only for as long as the process is running and is destroyed once it is no longer needed. The example with redirecting the file contents into
mysql above uses an anonymous pipe. A named pipe on the other hand is given a name and can last indefinitely. It’s created using special commands and often appears as a special file in the filesystem.
Regardless of the type, an important property of any pipe is that it’s a FIFO (first in, first out) structure. This means the data written into a pipe first by one process is the first data read out from the pipe by the other process.
Introducing proc_open()The PHP function
proc_open() executes a command, much like
exec() does, but with the added ability to direct input and output streams through pipes. It accepts some optional arguments, but the mandatory arguments are:
- a command to execute.
- an array that describes the pipes to be used.
- an array reference that will later be populated with references to the pipes’ endpoints so you can send/receive data.
proc_open() fills the third parameter’s array reference to return the resources to the process. The elements in the reference can be treated as normal file descriptors and they work with file and stream functions like
When you’re done interacting with the external command, it’s important to clean up after yourself. You can close the pipes (with
fclose()) and the process resource (with
Depending on how your target command/process behaves, you may need to close the STDIN connection before it begins its work (so it knows not to expect any more input). And, you should close the STDOUT and STDERR connections before closing the process, or it might hang while it waits for everything to be clean before shutting down.
A Practical Example: Converting Wiki MarkupSo far I’ve only talked about how things work, but I haven’t shown you an example using
proc_open() to spawn and communicate with an external process yet. So, let’s see how easy it is to use.
Suppose we have a need to convert a chunk of text with wiki markup to HTML for display in a user’s browser. We’re using the Nyctergatis Markup Engine (NME) to perform the conversion, but since it’s a compiled C binary we need a way to fire up
nme when its needed and a way to pass input and receive output.
First the descriptor array is laid out, with 0 (STDIN) as an anonymous pipe that will be readable by the markup engine, 1 (STDOUT) as an anonymous pipe writable by the engine, and 2 (STDERR) redirecting any error messages to an error log file.
The “r” and “w” on the pipe definitions might seem counter intuitive at first, but keep in mind they are the channels that the engine will be using and so are configured from it’s perspective. We write to the read pipe because the engine will be reading the data from it. We read from the write pipe because the engine has written data to it.
// descriptor array
$desc = array(
0 => array('pipe', 'r'), // 0 is STDIN for process
1 => array('pipe', 'w'), // 1 is STDOUT for process
2 => array('file', '/tmp/error-output.txt', 'a') // 2 is STDERR for process
// command to invoke markup engine
$cmd = "nme --strictcreole --autourllink --body --xref";
// spawn the process
$p = proc_open($cmd, $desc, $pipes);
// send the wiki content as input to the markup engine
// and then close the input pipe so the engine knows
// not to expect more input and can start processing
// read the output from the engine
$html = stream_get_contents($pipes);
// all done! Clean up
ConclusionThere are many ways to interact with external processes; some may be better than using
proc_open() given how and what you need to work with, or
proc_open() might just be what the doctor ordered for your situation. Of course you’ll implement what makes sense, but now you’ll know how to use this powerful function if you need to!
I’ve placed someexample code on GitHub that simulates a bare-bones wiki using NME, just like in the example above. Feel free to clone it if you’re interested in playing and exploring further.
Image via Fotolia
Frequently Asked Questions (FAQs) about proc_open in PHP
What is the primary function of proc_open in PHP?
The proc_open function in PHP is a powerful tool that allows PHP scripts to communicate with external programs. It opens a process file pointer, which can be used to execute a command and open file pointers for input/output. This function is particularly useful when you need to interact with system-level commands, other scripts, or standalone applications from within your PHP code.
How can I use proc_open to execute a command?
To use proc_open to execute a command, you need to pass the command as a string to the function, along with an array to hold the pipe descriptors and a variable to hold any potential errors. The function will return a resource type variable that represents the process, which can be used with other functions like proc_close and proc_get_status.
What are the potential security risks of using proc_open?
While proc_open is a powerful function, it can also pose security risks if not used properly. Since it allows execution of any command, it can potentially be used to execute malicious commands if user input is not properly sanitized. Always ensure to escape any user input with escapeshellarg or escapeshellcmd before using it in proc_open.
How can I handle errors with proc_open?
The proc_open function will return false if it fails to execute the command. You can check this return value to handle errors. Additionally, you can use the proc_get_status function to get information about the process, including whether it is still running and the exit code if it has terminated.
Can I use proc_open to interact with other PHP scripts?
Yes, proc_open can be used to interact with other PHP scripts. You can pass the path to the PHP script as the command to proc_open. The script will be executed in a separate process, and you can communicate with it through the provided pipes.
How can I read the output of a command executed with proc_open?
The proc_open function provides pipes that can be used to read the output of the command. These pipes are provided as an array to the function, and they can be read from or written to using standard file functions like fgets or fwrite.
Can I use proc_open to execute commands in the background?
Yes, proc_open can be used to execute commands in the background. This can be done by appending an ampersand (&) to the command. However, note that the pipes will not be available once the process is running in the background.
How can I close a process opened with proc_open?
You can close a process opened with proc_open using the proc_close function. This function takes the process resource returned by proc_open as an argument and closes the process.
Can I use proc_open with non-blocking I/O?
Yes, proc_open can be used with non-blocking I/O. This can be done by setting the stream to non-blocking mode using the stream_set_blocking function.
How can I use proc_open to write to the input of a command?
You can write to the input of a command executed with proc_open using the provided pipes. The second element of the descriptor array is a pipe that can be written to using standard file functions like fwrite.