Scheduled Tasks in SilverStripe

Creating a scheduled task in SilverStripe, one that is called by cron, can be a bit fiddly and the documentation seems to have disappeared. Here's the process I followed to create a SilverStripe scheduled task that is run by cron.  

Create your scheduled task

Create a class that extends ScheduledTask (or YearlyTask, MonthlyTask, WeeklyTask, DailyTask, HourlyTask or QuarterHourlyTask) it doesn't really matter that much. You may want to create this task in a tasks/ folder (e.g: mysite/tasks/) although I don't *think* it should matter.

After creating the class (and perhaps running a /dev/build?flush=1 for good measure) all you need to do is override the process() method to add some code that you want to process when the task is run.

I have created a RemoveAbandonedCartsTask for some shopping cart software I'm working on which queries the database for old carts that have been abandoned and deletes them:

function process(){

  date_default_timezone_set('Pacific/Auckland');
  $oneHourAgo = date('Y-m-d H:i:s', strtotime('-1 hour'));
  
  //Get orders that were last active over an hour ago and have not been paid at all
  $orders = DataObject::get(
    'Order',
    "Order.LastActive < '$oneHourAgo' AND Order.Status = 'Cart' AND Payment.ID IS NULL",
    '',
    "LEFT JOIN Payment ON Payment.OrderID = Order.ID"
  );

  foreach ($orders as $order) {
    $order->delete();
  } 
  echo 'Time one hour ago is: ' . $oneHourAgo . "\n";
} 

Test run the task from the command line

In order for this to work you will need the php cli installed on your machine. To check that it is installed you can run 'php -v' in your terminal/command line.

If the command 'php' is not recognised you will need to install php cli (somehow). Once that is sorted go to the command line and run a command similar to:

php /path/to/project/sapphire/cli-script.php /RemoveAbandonedCartsTask

The /path/to/project/ should be the complete path to the root of your SilverStripe install, the SilverStripe install where you have created the Task above.

You should see some output after the task is run. At this stage you might like to set up a log file to redirect the output of the task into, for example:

sudo touch /var/log/someproject.log
# Remember to set permissions correctly
php /path/to/project/sapphire/cli-script.php /RemoveAbandonedCartsTask > /var/log/someproject.log

I usually keep the log open in another terminal to view the output, for example:

cd /var/log/
tail -f someproject.log
# Run php /path/to/project/sapphire/cli-script.php /RemoveAbandonedCartsTask > /var/log/someproject.log in the other terminal
# The log should update with some output
# Hit ctrl-z when finished viewing the log

# An easy way to wipe the log clean
:>someproject.log
# And then clean up the history output in your terminal (on a mac) cmd-k

Setting up a cron job to run the task on schedule

All we need to do is edit the crontab to run the commands from above. A good brief explanation of crontab syntax lives here.

crontab -e
# In the editor add the line
*/1 * * * * php /path/to/project/sapphire/cli-script.php /RemoveAbandonedCartsTask > /var/log/someproject.log
# Save and close

This has scheduled the task to be run every minute, which is good for testing. Now is a good time to open a new terminal and start a tail -f running on your someproject.log.

Debugging the problems from cron

You might find that running your script in cron starts throwing warnings and errors everywhere (I did). Here are some of the problems I had and their solutions.

“unix:///var/mysql/mysql.sock” not found
My task could not connect to the database, because the mysql.sock file could not be found. I'm running MAMP on Mac OSX, the solution for this mysql.sock problem is actually very simple and can be found here.

date.timezone is not set or similar php.ini problems
Even though I had the date.timezone set in my php.ini, the php cli on my system uses a different php.ini file. To get around this particular issue I used date_default_timezone_set('Pacific/Auckland'); in my script. If you need to edit the php.ini for the php command line interface I think it lives at /private/etc/php.ini if you are using MAMP. You can also specify which ini file the php cli uses e.g:

php --php-ini /path/to/some/php.ini

HTTP_HOST not set
To fix this you need to set global $_FILE_TO_URL_MAPPING; - and this must be done from an _ss_environment.php file (doesn't seem to work from the _config.php file for some reason). Find out how to set file to URL mapping here, and more about the ss environment file here.

Useful Links
SilverStripe Queued Jobs - I haven't looked into this module but it sounds pretty cool
How to empty system mail on OSX - this is useful for clearing all the messages full of errors you might get