FAQ

FAQ

choose category

Using a pdf from a php variable instead of a file

The following article concerns only SetaPDF 1.x and FPDI 1.x. SetaPDF 2.x is shipped with separate reader classes which makes this workaround obsolete. FPDI 2.x can read PDFs also from any source

Sometimes PDF documents are not available through the filesystem which was a must for all old SetaPDF APIs and is still a must for FPDI.

With this solution the whole PDF has not to be hold in memory but only the parts which are actually needed. PDF documents could be very huge, while each PHP installation (mostly) have its memory limits - and reaching them will let the script die.

For some developers this issue isn't important, because they can adjust their memory limits as high as they want. Or other developers simply works with smal pdf documents which are saved in a blob field in a database and don't want to use temporary files in the middle of database and SetaPDF API or FPDI.

For this developers (and others) we'll describe a way, you can solve this problem with "simple" existing php features: stream wrappers

PHP
<?php

class VarStream {
    private $_pos;
    private $_stream;
    private $_cDataIdx;

    static protected $_data = array();
    static protected $_dataIdx = 0;
    
    static function createReference(&$var) {
        $idx = self::$_dataIdx++;
        self::$_data[$idx] =& $var;
        return __CLASS__.'://'.$idx;
    }
    
    static function unsetReference($path) {
        $url = parse_url($path);
        $cDataIdx = $url["host"];
        if (!isset(self::$_data[$cDataIdx]))
            return false;
            
        unset(self::$_data[$cDataIdx]);
        
        return true;
    }

    public function stream_open($path, $mode, $options, &$opened_path) {
        $url = parse_url($path);
        $cDataIdx = $url["host"];
        if (!isset(self::$_data[$cDataIdx]))
            return false;
        $this->_stream = &self::$_data[$cDataIdx];
        $this->_pos = 0;
        if (!is_string($this->_stream)) return false;
        $this->_cDataIdx = $cDataIdx;
        return true;
    }

    public function stream_read($count) {
        $ret = substr($this->_stream, $this->_pos, $count);
        $this->_pos += strlen($ret);
        return $ret;
    }

    public function stream_write($data) {
        $l=strlen($data);
        $this->_stream =
            substr($this->_stream, 0, $this->_pos) .
            $data .
            substr($this->_stream, $this->_pos += $l);
        return $l;
    }

    public function stream_tell() {
        return $this->_pos;
    }

    public function stream_eof() {
        return $this->_pos >= strlen($this->_stream);
    }

    public function stream_seek($offset, $whence) {
        $l=strlen($this->_stream);
        switch ($whence) {
            case SEEK_SET: $newPos = $offset; break;
            case SEEK_CUR: $newPos = $this->_pos + $offset; break;
            case SEEK_END: $newPos = $l + $offset; break;
            default: return false;
        }
        $ret = ($newPos >= 0 && $newPos <= $l);
        if ($ret) $this->_pos=$newPos;
        return $ret;
    }
    
    public function url_stat($path, $flags) {
        $url = parse_url($path);
        $dataIdx = $url["host"];
        if (!isset(self::$_data[$dataIdx]))
            return false;

        $size = strlen(self::$_data[$dataIdx]);
        return array(
            7 => $size,
            'size' => $size
        );
    }
    
    public function stream_stat() {
        $size = strlen($this->_stream);
        
        return array(
            'size' => $size,
            7 => $size,
        );
    }
}

stream_wrapper_register('VarStream', 'VarStream') or die('Failed to register protocol VarStream://');

Now you can simply use the new registered wrapper this way:

PHP
$varInAnyScope = [PDF-Document];

$fpdi->setSourceFile(VarStream::createReference($varInAnyScope));

// or

$merger->addFile(VarStream::createReference($varInAnyScope));

// or

$newVarInAnyScope = '';
$stamper->addFile(array(
    'in'  => VarStream::createReference($varInAnyScope),
    'out' => VarStream::createReference($newVarInAnyScope)
));