Perforce Chronicle 2012.2/486814
API Documentation

Cron_Model_Cron Class Reference

Provides infrastructure for executing periodic time-based tasks in the application. More...

Inheritance diagram for Cron_Model_Cron:
P4Cms_Record P4Cms_Record_Connected P4Cms_Model P4Cms_ModelInterface

List of all members.

Public Member Functions

 getCompleted ()
 Get info whether last cron execution was completed.
 getData ()
 Get cron data.
 getRunTime ()
 Get cron runTime (timestamp of the most recent cron execution).
 setCompleted ($completed)
 Set whether last cron execution was completed or not.
 setData (array $data)
 Set additional data about the cron execution.
 setRunTime ($timestamp)
 Set runTime timestamp.

Static Public Member Functions

static run (array $frequencies=null, $timestamp=null, P4Cms_Record_Adapter $adapter=null)
 Run cron (if needed) for given frequencies specified in the optional parameter (all by default).

Public Attributes

const CRON_DAILY = 'daily'
const CRON_HOURLY = 'hourly'
const CRON_MONTHLY = 'monthly'
const CRON_WEEKLY = 'weekly'
const RESULT_EXECUTED = 'executed'
const RESULT_FAILED = 'failed'
const RESULT_SKIPPED = 'skipped'

Static Protected Member Functions

static _needRun (array $frequencies, $timestamp, P4Cms_Record_Adapter $adapter=null)
 Check if cron needs to be run for given frequencies.
static _run ($frequency, $timestamp, P4Cms_Record_Adapter $adapter=null)
 Execute cron for a given frequency.
static _truncateTimestamp ($timestamp, $frequency)
 Return timestamp truncated with respect to the given frequency.

Static Protected Attributes

static $_fields
 Specifies the array of fields that the current Record class wishes to use.
static $_storageSubPath = 'cron'
 Specifies the sub-path to use for storage of records.

Detailed Description

Provides infrastructure for executing periodic time-based tasks in the application.

Tasks are executed by third-parties via subscribing to the 'p4cms.cron.<FREQUENCY>' topics, where <FREQUENCY> determines time-periods of publishing those topics.

Currently, model handles the following frequencies:

hourly - 'p4cms.cron.hourly' topic is published (at the most) once per hour daily - 'p4cms.cron.daily' topic is published (at the most) once per day weekly - 'p4cms.cron.weekly' topic is published (at the most) once per week monthly - 'p4cms.cron.monthly' topic is published (at the most) once per month

Pub-sub topics are published by calling Cron_Model_Cron::run() method, that publishes only topics there were previously published sufficiently long ago according to the frequency. This method also returns a report, to check which topics were actually published, in the form of associative array with frequencies as keys and statuses as values, where status is one of the following:

executed - if topic was successfully published, skipped - if topic was not published (ie. previously executed) failed - if there were errors during publishing the topic (however it doesn't necessarily mean that topic was published)

Every time the topic is published, 2 new revisions of the cron file (for each frequency) are created. One just before and one just after publishing the topic. At the first update, execution timestamp is saved and 'completed' attribute is set to false. At the second update, 'completed' attribute is set to true and data collected from subscribers are stored in the 'data' attribute.

Whereas this module guarantees that particular topics are not published too often (i.e. not more than once per hour for p4cms.cron.hourly topic, not more than once per day for p4cms.cron.daily topic etc.), it does not guarantee that 'p4cms.cron.hourly' topic will be published every hour and similarly for other frequencies, as it depends on calling Cron_Model_Cron::run() method sufficiently often.

Typically, this can be ensured by setting up a cron job on a server with a frequency of at least once per hour. For example:

0 * * * * wget -O - -q -t 1 http://example.com/cron

The above line executes wget once every hour (at the 'top' of the hour). The '-O -' arguments tell wget to write output to standard out, '-q' runs quietly and '-t 1' limits retries to one. If you do not have wget, try curl with comparable options.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

static Cron_Model_Cron::_needRun ( array $  frequencies,
timestamp,
P4Cms_Record_Adapter adapter = null 
) [static, protected]

Check if cron needs to be run for given frequencies.

Returns list of only those frequencies the cron needs to be run for.

Parameters:
array$frequencieslist of frequencies to check for
string$timestampunix timestamp to run cron at
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
array list of frequencies from $frequencies the cron needs to be run for.
    {
        // if no adapter given, use default.
        $adapter = $adapter ?: static::getDefaultAdapter();

        // fetch existing cron files for given frequencies
        $records = static::fetchAll(array('ids' => $frequencies));

        // assume cron needs to be run for all frequencies that don't have cron
        // record yet
        $needRun = array_diff($frequencies, $records->invoke('getId'));

        // iterate over existing cron files for given frequencies and determine
        // which of them need to be updated by comparing truncated timestamp
        // of the last cron run with truncated $timestamp provided
        foreach ($records as $cron) {
            $runTime            = $cron->getRunTime();
            $frequency          = $cron->getId();
            $truncatedRunTime   = static::_truncateTimestamp($runTime,   $frequency);
            $truncatedTimestamp = static::_truncateTimestamp($timestamp, $frequency);

            // cron needs to be updated if truncated timestamps are different
            if ($truncatedRunTime !== $truncatedTimestamp) {
                $needRun[] = $frequency;
            }
        }

        return $needRun;
    }
static Cron_Model_Cron::_run ( frequency,
timestamp,
P4Cms_Record_Adapter adapter = null 
) [static, protected]

Execute cron for a given frequency.

Publishes 'p4cms.cron.<frequency>' topic to let subscribers executing their cron tasks. Subscribers can optionally return list of messages that will be saved in the cron log file.

Parameters:
string$frequencyfrequency to run cron for (it should be one of hourly|daily|weekly|monthly)
string$timestampunix timestamp to run cron at (useful for testing, in real case this should refer to the current time)
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.

p4cms.cron.hourly Perform periodic operations intended to execute once per hour. Any returned text will be saved in the cron log. (requires additional settings on a server, or activation of the Easy_Cron module. Please see <xref linkend="site.time-based-tasks">)

p4cms.cron.daily Perform periodic operations intended to execute once per day. Any returned text will be saved in the cron log. (requires additional settings on a server, or activation of the Easy_Cron module. Please see <xref linkend="site.time-based-tasks">)

p4cms.cron.weekly Perform periodic operations intended to execute once per week. Any returned text will be saved in the cron log. (requires additional settings on a server, or activation of the Easy_Cron module. Please see <xref linkend="site.time-based-tasks">)

p4cms.cron.monthly Perform periodic operations intended to execute once per month. Any returned text will be saved in the cron log. (requires additional settings on a server, or activation of the Easy_Cron module. Please see <xref linkend="site.time-based-tasks">)

    {
        // if no adapter given, use default.
        $adapter = $adapter ?: static::getDefaultAdapter();

        // prepare messages to use later
        $cronStarted   = "Cron $frequency started.";
        $cronCompleted = "Cron $frequency completed.";

        // update cron file immediately, so if another cron check is started
        // before this run is done, it won't be executed again
        $cron = new static;
        $cron->setAdapter($adapter)
             ->setId($frequency)
             ->setRunTime($timestamp)
             ->setCompleted(false);

        // set filetype to ctext to improve performance as there may be a huge
        // number of revisions in the future
        $file = $cron->_getP4File();
        $file->touchLocalFile()
             ->open(null, 'text+C');

        $cron->save($cronStarted);

        // publish cron topic to allow subscribers to execute their cron tasks
        // collect messages that subscribers can optionally return
        P4Cms_Log::log($cronStarted, P4Cms_Log::NOTICE);
        $topic    = 'p4cms.cron.' . $frequency;
        $messages = P4Cms_PubSub::publish($topic);

        // add collected messages and save cron as completed
        $cron->setData($messages)
             ->setCompleted(true)
             ->save($cronCompleted);
        P4Cms_Log::log($cronCompleted, P4Cms_Log::NOTICE);
    }
static Cron_Model_Cron::_truncateTimestamp ( timestamp,
frequency 
) [static, protected]

Return timestamp truncated with respect to the given frequency.

Following parts from the timestamp are truncated for the given frequencies:

hourly: minutes + seconds daily: hours + minutes + seconds weekly: days in the week (since Monday) + hours + minutes + seconds monthly: days in the month + hours + minutes + seconds

Example: January 2nd 2011 15:45:27 will be truncated to:

January 2nd 2011 15:00:00 for the hourly frequency, January 2nd 2011 00:00:00 for the daily frequency, December 26th 2010 00:00:00 for the weekly frequency and January 1st 2011 00:00:00 for the monthly frequency

Parameters:
int$timestampunix timestamp to truncate
string$frequencyfrequency to truncate $timestamp according to
Returns:
int|null unix timestamp of truncated $timestamp with respect to $frequency or null if not able to truncate
    {
        // get formatted string representing datetime of the truncated timestamp
        switch ($frequency) {
            case static::CRON_HOURLY:
                $datetime = date('Y-m-d H:00', $timestamp);
                break;
            case static::CRON_DAILY:
                $datetime = date('Y-m-d', $timestamp);
                break;
            case static::CRON_WEEKLY:
                $datetime = date('Y-\WW', $timestamp);
                break;
            case static::CRON_MONTHLY:
                $datetime = date('Y-m', $timestamp);
                break;
        }

        // convert datetime to the timestamp and return
        return isset($datetime) ? strtotime($datetime) : null;
    }
Cron_Model_Cron::getCompleted ( )

Get info whether last cron execution was completed.

Returns:
boolean true if last cron execution was completed, false otherwise
    {
        return (boolean) $this->_getValue('completed');
    }
Cron_Model_Cron::getData ( )

Get cron data.

Returns:
array list of data about cron execution.
    {
        return $this->_getValue('data');
    }
Cron_Model_Cron::getRunTime ( )

Get cron runTime (timestamp of the most recent cron execution).

Returns:
int runTime timestamp
    {
        return (int) $this->_getValue('runTime');
    }
static Cron_Model_Cron::run ( array $  frequencies = null,
timestamp = null,
P4Cms_Record_Adapter adapter = null 
) [static]

Run cron (if needed) for given frequencies specified in the optional parameter (all by default).

Every cron run (for a given frequency) updates cron/<FREQUENCY> record, where <FREQUENCY> is one of hourly|daily|weekly|monthly.

Parameters:
array$frequencies(optional) list of frequencies to run cron for, defaults to all
int$timestamp(optional) UNIX timestamp to use for determining if cron needs to be run (useful mostly for testing); if not provided, then current timestamp will be used
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
array list with run report for each frequency, report is one of executed|skipped|failed.
    {
        // if no adapter given, use default.
        $adapter = $adapter ?: static::getDefaultAdapter();

        $all = array(
            static::CRON_HOURLY,
            static::CRON_DAILY,
            static::CRON_WEEKLY,
            static::CRON_MONTHLY
        );

        // filter for only valid frequencies, fall back to all frequency types if none passed
        $frequencies = array_intersect($frequencies ?: $all, $all);

        // if no timestamp is provided, set to current
        if (!$timestamp) {
            $timestamp = time();
        }

        // get list of frequencies to run cron for
        $needRun = static::_needRun($frequencies, $timestamp, $adapter);

        // iterate over all frequencies and run cron if needed
        $report = array();
        foreach ($frequencies as $frequency) {
            if (!in_array($frequency, $needRun)) {
                $report[$frequency] = static::RESULT_SKIPPED;
                continue;
            }

            // cron jobs should run as a special type of anonymous
            // user that can be granted additional privileges.
            $activeUser = P4Cms_User::hasActive()
                ? P4Cms_User::fetchActive()
                : null;
            P4Cms_User::setActive(new Cron_Model_User);

            try {
                static::_run($frequency, $timestamp, $adapter);
                $result = static::RESULT_EXECUTED;
            } catch (Exception $e) {
                P4Cms_Log::logException("Cron run failed [frequency: $frequency].", $e);
                $result = static::RESULT_FAILED;
            }

            // after each cron job, restore original active user.
            if ($activeUser) {
                P4Cms_User::setActive($activeUser);
            } else {
                P4Cms_User::clearActive();
            }

            $report[$frequency] = $result;
        }

        return $report;
    }
Cron_Model_Cron::setCompleted ( completed)

Set whether last cron execution was completed or not.

Parameters:
boolean$completedset true if last cron execution was completed, false otherwise
Returns:
Cron_Model_Cron provides fluent interface
    {
        $this->_setValue('completed', $completed ? '1' : '0');
        return $this;
    }
Cron_Model_Cron::setData ( array $  data)

Set additional data about the cron execution.

Parameters:
array$datadata to record with cron execution
Returns:
Cron_Model_Cron provides fluent interface
    {
        $this->_setValue('data', $data);
        return $this;
    }
Cron_Model_Cron::setRunTime ( timestamp)

Set runTime timestamp.

Parameters:
int$timestampunix timestamp
Returns:
Cron_Model_Cron provides fluent interface
    {
        if (!is_int($timestamp) || $timestamp < 0) {
            throw new InvalidArgumentException("Timestamp must be a non-negative integer");
        }

        $this->_setValue('runTime', $timestamp);
        return $this;
    }

Member Data Documentation

Cron_Model_Cron::$_fields [static, protected]
Initial value:
 array(
        'runTime'       => array(
            'accessor'  => 'getRunTime',
            'mutator'   => 'setRunTime'
        ),
        'completed'     => array(
            'accessor'  => 'getCompleted',
            'mutator'   => 'setCompleted'
        ),
        'data'          => array(
            'accessor'  => 'setData',
            'mutator'   => 'getData'
        )
    )

Specifies the array of fields that the current Record class wishes to use.

The implementing class MUST set this property.

Reimplemented from P4Cms_Record.

Cron_Model_Cron::$_storageSubPath = 'cron' [static, protected]

Specifies the sub-path to use for storage of records.

This is used in combination with the records path (provided by the storage adapter) to construct the full storage path. The implementing class MUST set this property.

Reimplemented from P4Cms_Record.

const Cron_Model_Cron::CRON_DAILY = 'daily'
const Cron_Model_Cron::CRON_HOURLY = 'hourly'
const Cron_Model_Cron::CRON_MONTHLY = 'monthly'
const Cron_Model_Cron::CRON_WEEKLY = 'weekly'

The documentation for this class was generated from the following file: