Search code examples
phppdftk

PDFTK with php://memory


Question: Is it possible to use php://memory on a exec or passthru command?

I can use php variables in the exec or passthru with no problem, but I am having trouble with php://memory

background: I am trying to eliminate all of my temporary pdf file writing with PDFTK.
1)I am writing an temporary fdf file
2) form-fill a temporary pdf file using #1
3) repeat #1 and #2 for all the pdfs
4) merge all pdf's together.

This currently works - but it creates a lot of files, and is the bottleneck.

I would like to speed things up with pdftk by making use of the virtual file php://memory

First, I am trying to just virtualize the fdf file used in #1.
Answering this alone is enough for a 'correct answer'.   :)

The code is as follows:

$fdf = 'fdf file contents here';
$tempFdfVirtual= fopen("php://memory", 'r+');
if(  $tempFdfVirtual ) {
  fwrite(  $tempFdfVirtual, $fdf);
} else {
    echo "Failure to open temporary fdf file";
    exit;
}
rewind( $tempFdfVirtual);
$url = "unfilled.pdf";
$temppdf_fn = "output.pdf"; 
$command = "pdftk $url fill_form  $tempFdfVirtual output $temppdf_fn flatten"; 
$error="";   
exec( $command, $error );
if ($error!="") {
    $_SESSION['err'] = $error;       
} else {
    $_SESSION['err'] = 0;
}

I am getting an errorcode #1. If I do a stream_get_contents($tempFdfVirtual), it shows the contents.

Thanks for looking!


Solution

  • php://memory and php://temp (and in fact any file descriptor) are only available to the currently-running php process. Besides, $tempFdfVirtual is a resource handle so it makes no sense to put it in a string.

    You should pass the data from your resource handle to the process through its standard-in. You can do this with proc-open, which gives you more control over input and output to the child process than exec.

    Note that for some reason, you can't pass a 'php://memory' file descriptor to a process. PHP will complain:

    Warning: proc_open(): cannot represent a stream of type MEMORY as a File Descriptor

    Use php://temp instead, which is supposed to be exactly the same except it will use a temporary file once the stream gets big enough.

    This is a tested example that illustrates the general pattern of code that uses proc_open(). This should be wrapped up in a function or other abstraction:

    $testinput = "THIS IS A TEST STRING\n";
    
    $fp = fopen('php://temp', 'r+');
    fwrite($fp, $testinput);
    rewind($fp);
    
    $cmd = 'cat';
    $dspec = array(
        0 => $fp,
        1 => array('pipe', 'w'),
    );
    $pp = proc_open($cmd, $dspec, $pipes);
    
    // busywait until process is finished running.
    do {
        usleep(10000);
        $stat = proc_get_status($pp);
    } while($stat and $stat['running']);
    
    if ($stat['exitcode']===0) {
        // index in $pipes will match index in $dspec
        // note only descriptors created by proc_open will be in $pipes
        // i.e. $dspec indexes with an array value.
        $output = stream_get_contents($pipes[1]);
        if ($output == $testinput) {
            echo "TEST PASSED!!";
        } else {
            echo "TEST FAILED!! Output does not match input.";
        }
    } else {
        echo "TEST FAILED!! Process has non-zero exit status.";
    }
    
    // cleanup
    // close pipes first, THEN close process handle.
    foreach ($pipes as $pipe) {
        fclose($pipe);
    }
    // Only file descriptors created by proc_open() will be in $pipes.
    // We still need to close file descriptors we created ourselves and
    // passed to it.
    // We can do this before or after proc_close().
    fclose($fp);
    proc_close($pp);
    

    Untested Example specific to your use of PDFTK:

    // Command takes input from STDIN
    $command = "pdftk unfilled.pdf fill_form - output tempfile.pdf flatten"; 
    $descriptorspec = array(
        0 => $tempFdfVirtual, // feed stdin of process from this file descriptor
    //    1 => array('pipe', 'w'), // Note you can also grab stdout from a pipe, no need for temp file
    );
    $prochandle = proc_open($command, $descriptorspec, $pipes);
    // busy-wait until it finishes running
    do {
        usleep(10000);
        $stat = proc_get_status($prochandle);
    } while ($stat and $stat['running']);
    
    if ($stat['exitcode']===0) {
        // ran successfully
        // output is in that filename
        // or in the file handle in $pipes if you told the command to write to stdout.
    }
    
    // cleanup
    foreach ($pipes as $pipe) {
       fclose($pipe);
    }
    proc_close($prochandle);