Create zip archive with cron

Hi,
I’m trying to create a zip archive with php using cron jobs. So far what I’ve done is get a json string from rabbitmq create pdf files and store into a dirctory, and then I need to add these files into a zip file so it can be downloaded in the future. I’ve tested the script calling it from the browser and it works fine but when i try to run it with cron jobs then it creates all the pdf files but doesn’t create the zip file. I believe the problem could be in the way cron jobs manage the directories. The php file will be outside the public directory, at the moment I’m testing it on my local machine using MAMP
This is the script:

// Load config file
require dirname(__DIR__, 2) . '/km-config/km-config.php';
// Load composer autoloader
require dirname(__DIR__) . '/km-composer/vendor/autoload.php';
// Load email functions
require dirname(__DIR__) . '/km-functions/km-email-functions.php';
// Load email functions
require dirname(__DIR__) . '/km-functions/km-string-functions.php';

//Connect to CloudAMQP server
$queue = new CloudAMQP;
$queue->CloudAMQP_PDF_Connect(AMQP_HOST, AMQP_PORT, AMQP_USER, AMQP_PASSWORD, AMQP_VHOST);

// $kmg_zip_directory_array = array();

// Process messages from the queue
$queue->CloudAMQP_PDF_Process(function($message) {

	$messageBody = json_decode($message->body);

      $kmg_user_firstname = $messageBody->kmg_pdf_user_firstname;
      $kmg_user_lastname = $messageBody->kmg_pdf_user_lastname;
      $kmg_user_username = $messageBody->kmg_pdf_user_username;
      $kmg_user_password = $messageBody->kmg_pdf_user_password;
      $kmg_zip_directory = $messageBody->kmg_pdf_zip_directory;

        // Check if a subdirectory with subdomain name exst otherwise create one
        if (!file_exists(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory)) {
            // Make new directory with subdomain name
            mkdir(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory, 0755, true);

            if(!file_exists(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory.'/km-imported-owners-letters')){
                // Make new directory with subdomain name
                mkdir(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory.'/km-imported-owners-letters', 0755, true);

            }

        }else{

            if(!file_exists(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory.'/km-imported-owners-letters')){
                // Make new directory with subdomain name
                mkdir(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory.'/km-imported-owners-letters', 0755, true);

            }

        }

      // Make sure we decrypt password coming from CloudAMQP
      $km_user_decrypted_password = km_encrypt_decrypt('decrypt', $kmg_user_password);

      // Create file name structure
      $filename = 'Lettera-benvenuto-'.$kmg_user_firstname.'-'.$kmg_user_lastname.'-'.time();

      // Call class mpdf to create pdf
      $mpdf = new \Mpdf\Mpdf();

      // Load the email template
      $km_email_message = file_get_contents(dirname(__DIR__, 2).'/km-views/km-letters/km-letter-new-user.html');

      $km_email_message = str_replace('%client_first_name%', $kmg_user_firstname, $km_email_message);
      $km_email_message = str_replace('%client_last_name%', $kmg_user_lastname, $km_email_message);
      $km_email_message = str_replace('%client_username%', $kmg_user_username, $km_email_message);
      $km_email_message = str_replace('%client_password%', $km_user_decrypted_password, $km_email_message);

      $mpdf->WriteHTML($km_email_message);
      $mpdf->Output(dirname(__DIR__, 2).'/km-documents/'.$kmg_zip_directory.'/km-imported-owners-letters/'.$filename,'F');

   
       // array_push($kmg_zip_directory_array, $kmg_zip_directory);
   


});


            $kmg_zip_password = 'pippo';


            // Get real path for our folder
            $rootPath = realpath(dirname(__DIR__, 2).'/km-documents/router/km-imported-owners-letters');


            // Initialize archive object
            $zip = new ZipArchive();
            $zip->open(dirname(__DIR__, 2).'/km-documents/router/lettere-proprietari-importati.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);

            $zip->setPassword($kmg_zip_password);

            // 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 from folder to archive
                    $zip->addFile($filePath, $relativePath);
                    // Set up password for the archive
                    $zip->setEncryptionName($relativePath, ZipArchive::EM_AES_256);

                    // Add pdf files form directory to array so I can delete them once I've imported them to zip
                    $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);
            }

$queue->CloudAMQP_Disconnect();

I had problems with cron jobs and directories and used the full directory/path instead of relative paths, etc and it solved my problem.

Also try echoing getcwd() while running the cron job and it should render the directory.

Hi @John_Betong thanks for your replay. How can I set the full directory into a variable? I tried to run
echo getcwd(); but where do I see the the result?

Look in the log files.

Edit:
Also try:

  1. replacing the require dirname(__DIR__) with the complete path
  2. then echo getcwd()

Check the results.

If the file zips OK then replace the local paths with the online paths.

Hi,
I use lingon to run the cron job on local machine. I don’t really know where the cron log file sare located. If I can manage to see the echo output then I will be able to understand where the problem can be. It if frustrating

Hi @John_Betong so if I run the php script to create the zip file from a differente cron job then it works but if I want to put in the same file to create the zip file after create PDF then it doesn’t work. Not sure why

Try and amend the zip class method return a logical true/false result on success.

If and only if that is not the problem then I have tested the cron task script below.

It works fine for a standalone PHP file and writes cron messages to the "my_errors.log when the cron task is activated.

To set the cron task to execute every minute - BEWARE: paths may need changing

Set the above crontab:

  1. make sure it is working - check the output of /var/log/syslog
  2. make sure the /var/www/aatests/vince/ is writable
  3. examine /var/www/aatests/vince/my_errors.log
  4. gradually introduce your own script and test for errors
  5. report back on the results
<?php 
declare(strict_types=1);    // fail fast
error_reporting(-1);        // maximum errors
ini_set("log_errors", '1'); // display on screen

define('jj', "\n <br>");
  
$errorLog =  "/var/www/aatests/vince/my_errors.log";

error_log( "\n\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);

//=============================================================================
  try {

    error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);

  } catch (Exception $e) {
    $msg = $e->getMessage();
    echo "\nCaught exception: ",  $msg, "\n";
    error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);
  }
//=============================================================================

error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);

// OUTPUT/ECHO TO CONSOLE 
   echo jj .'SCREEN $ok ==> ' .__line__;

Hi @John_Betong thanks for your answer, I believe the problem can be with the way RabbitMQ processes the queue in the consumer, as it search for messages in the queue with a while loop.

In the class CloudAMQP I’ve got the following function:

        public function CloudAMQP_PDF_Process($callback){
            $this->channel->basic_consume(AMQP_PDF_QUEUE, '', false, true, false, false, $callback);
            
            try {

                while (count($this->channel->callbacks)) {
                    //5sec timeout
                    $this->channel->wait(null, false, 5);

                }

            }
            catch(PhpAmqpLib\Exception\AMQPTimeoutException $e){
                // echo $e;
            }

So maybe the reason why the zip file is not created is because there is an infinite loop started here:

$queue->CloudAMQP_PDF_Process(function($message)

Now the question is how can i exit the loop? Maybe I can use something like

register_shutdown_function('function_to_create_zip', $channel, $connection);

To run the zip creation when the loop activity has been compleated, I haven’t tested it yet

Hi Vince,

Did you try running the debug script I supplied and also checked the generated results?

As mentioned, once the deug script was working then introduce your script and verify the results.

I believe the problem can be with the way RabbitMQ processes the queue in the consumer, as it search for messages in the queue with a while loop.

What makes you think the problem is within the function?

So maybe the reason why the zip file is not created is because…

It is a wasted effort trying to guess - just insert the script into the debug script and ensure the zip is working.

Now the question is how can i exit the loop?

Because you have already proved the script works when called from PHP and fails when using the cron task - I would be tempted to add the following lines in the cron task before and after the call to the function:


error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);
//Connect to CloudAMQP server
$queue = new CloudAMQP;
error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);

$queue->CloudAMQP_PDF_Connect(AMQP_HOST, AMQP_PORT, AMQP_USER, AMQP_PASSWORD, 
AMQP_VHOST);
error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);

// Process messages from the queue
$queue->CloudAMQP_PDF_Process(function($message) {
error_log( "\nLine: " .__line__  .' ==> ' .date('H:i:s'), 3, $errorLog);


Unfortunately debugging is very, very tedious and necessary to eliminate all problems y validating each step before proceeding.

Hi @John_Betong yes I agree with you and i’m Going to try what you suggested. At the moment what I have done is putting some echoes in the script and I can see the one I place after the RabbitMQ while loop is not fired that is why I think the problem is in the while loop as RabbitMQ constantly searches for new messages from the queue

Is the class getting loaded? Maybe echo/send the queue variable to the error_log and compare the values with the PHP script.

Maybe it is not possible to load the remote class :frowning:

Hi @John_Betong so, I’ve tested the script calling it from the browser and it works fine, there must be a problem with cron jobs then

Where it is working in the PHP version, find and replace the paths in the from version.

echo dirname(__DIR__, 2) . '/km-config/km-config.php';
// Use this path in the from version

//Also do not use /vendor autoload.php
// Find a replacement

Hi @John_Betong I’ve sorted it out, do you want to know where the problem was?

// Get real path for our folder
            $rootPath = realpath(dirname(__DIR__, 2).'/km-documents/router/km-imported-owners-letters');

supposed to be:

// Get real path for our folder
            $rootPath = realpath(dirname(__DIR__, 2).'/km-documents/router/km-imported-owners-letters/');

Just missed a / at the end of the url :frowning:

I want to say thank you for your patience!! Many thanks

1 Like

Congratulations, you done very well in fixing the problem.

As mentioned…

I have previously struggled with Cron tasks because they ignore error reporting, etc. Far better to have strict error reporting to find the errors immediately and aware of the warnings which tend to bite late Friday afternoon :slight_smile:

1 Like