Create multiple pdf files with php

Hi,
I need to create multiple pdf files with php, I’ve achieved it with the following code using mpdf library. The only problem is that the PDF files are created after user sibmit a form and if is a lot of files (I’ve tested it with 50) then the scripts takes some time (2min) and I believe this can cause problem with server execution time and also for the user that has to wait such a long time.
Do you have any suggestion how I can solve the problem? I did find this link https://medium.com/@Jessicawlm/how-to-generate-a-large-pdf-file-with-php-ed9ee7aab41b where they suggest to use wkhtmltopdf but not sure if I can install it on shared server. I was also thinking maybe I can show the content of each PDF file into a html page and then the user can download (I can use javascript for it)

This is my code


        // Query to database to insert user
        $query= "INSERT INTO km_users (
                             km_user_first_name,           
                             km_user_last_name,
                             km_user_fiscalcode,
                             km_user_document,
                             km_user_document_number,
                             km_user_document_exp,
                             km_user_birth_place,
                             km_user_birth_date,
                             km_user_telephone,
                             km_user_mobile,
                             km_user_address,
                             km_user_postcode,
                             km_user_city,
                             km_user_city_prov,
                             km_user_username,
                             km_user_password,
                             km_user_role,
                             km_user_email,
                             km_user_website,
                             km_user_active,
                             km_user_activation_code) 
                             VALUES (?,?,'','','',NULL,'',NULL,'','','','','','',?,?,'4',?,'','1',NULL)";

        if($stmt = mysqli_prepare($db_user_conn, $query)){
$time_start = microtime(true);
            foreach($kmg_owner_firstname as $index => $value) {

                // Create a random username if admin decide to create a online profile for the user
                $km_random_username = ($kmg_create_owner_profile == 1) ? random_int(100000, 999999) : NULL;
                // Set default password value to null
                $km_random_password = NULL;
                // Create a random password if admin decide to create a online profile for the user
                if($kmg_create_owner_profile == 1){
                    $km_generate_random_password = km_random_password();
                    $km_random_password = password_hash($km_generate_random_password, PASSWORD_DEFAULT);
                }

                mysqli_stmt_bind_param($stmt, 'sssss', $kmg_owner_firstname[$index], $kmg_owner_lastname[$index], $km_random_username, $km_random_password, $kmg_owner_email[$index]);

                mysqli_stmt_execute($stmt);

                // Get last user id for each user imported into the database
                $user_id= filter_var(mysqli_insert_id($db_user_conn), FILTER_SANITIZE_NUMBER_INT);

                // Push user id into the array
                array_push($user_ids, $user_id);

                // Check if admin wants to creade pdf letters
                if($km_create_owner_letter == 1){

                    $filename = time().'Performance_review.pdf';
                    sleep(2);
                    $mpdf = new \Mpdf\Mpdf(); 

                         // Render the view to create the html letter
                        $html= $twig->render('/km-letters/km-letter-new-user.twig', array(
                            // Render user details
                            'user' => array(
                                'firstName' => $kmg_owner_firstname[$index],
                                'lastName' => $kmg_owner_lastname[$index],
                                'userName' => $km_random_username,
                                'userPassword' => $km_generate_random_password
                            )
                         
                        ));
                    
                    $mpdf->WriteHTML($html);
                    $get_pdf = $mpdf->Output($km_files_path.KM_DS.$filename,'F');

                }



            }

        }else{

            throw new Exception('Si è verificato un errore nel tentativo di interrogare il database! Per favore riprova più tardi!', KM_ERROR_CODE);

        }





                    // Get real path for our folder
                    $rootPath = realpath($km_files_path);

                    // Initialize archive object
                    $zip = new ZipArchive();
                    $zip->open($km_folder_path.'file.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);

                    // Initialize empty "delete list"
                    $filesToDelete = array();

                    // Create recursive directory iterator
                    /** @var SplFileInfo[] $files */
                    $files = new RecursiveIteratorIterator(
                             new RecursiveDirectoryIterator($rootPath),
                             RecursiveIteratorIterator::LEAVES_ONLY
                    );

                    foreach ($files as $name => $file){
                        // Skip directories (they would be added automatically)
                        if (!$file->isDir()){
                            // Get real and relative path for current file
                            $filePath = $file->getRealPath();
                            $relativePath = substr($filePath, strlen($rootPath) + 1);

                            // Add current file to archive
                            $zip->addFile($filePath, $relativePath);
                            // Delete pdf files form directory
                            $filesToDelete[] = $filePath;
                        }
                    }

                    // Zip archive will be created only after closing object
                    $zip->close();

                    // Delete all files from "delete list"
                    foreach ($filesToDelete as $file){
                        unlink($file);
                    }

Yeah, you definitely don’t want the user waiting for the script to finish. One way to do it would be to create a queue (eg. in a database table) then use cron to run a script every minute (for example) to check the queue then create the PDFs accordingly.

Alternatively you could use exec() to run the script in the background, eg:

exec('script.php >/dev/null 2>/dev/null &');

The >/dev... bit means any output from the script is ignored, so the script runs in the background and the original page can finish processing independently.

Either way you’ll need a way of getting the PDFs to the user. One option would be to email them once the files are ready. Another would be a page that refreshes every few seconds and updates when the files are ready. Or use AJAX rather than reloading the whole page.

1 Like

Yes a was thinking about using somethings like cloud AMQP which i’m Using for email queue

  1. Client sends data with AJAX and shows progress bar.

  2. Server opens creating process(es) with popen().

  3. Any creating process checks its state in DB.

  4. Client asks with AJAX for actual creating state, modifies progress bar and fires “creating finish” if reached.

Hi @igor_g no idea how to achieve this

I would just go with the AMQP solution. If you’re already comfortable with that, that would be by far the easiest.

I would recommend to create one job per PDF, and not lump all of them in one big job. Throughput of many small jobs is generally better than the throughput of a few big jobs.

Hi @rpkamp thanks for your reply. Yes I’m still learning AMQP, the only problem I seem to have at the moment is figure out how to stop the consumer if I don’t have messages in the queue. I beleieve if I run the consumer all the time on shared host can cause some memory problems as well.

What people normally do is run under a deamon that keeps the workers running (i.e. start a new one when it stops)

  • After each job, check memory consumption. If it’s to high, stop.
  • Stop after n jobs

Then when the worker stops, a new worker will be created by the daemon.

Usually people use Supervisord for this, but there are others as well.