You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
7.3 KiB
231 lines
7.3 KiB
<?php
|
|
|
|
require_once INCLUDE_DIR . 'class.orm.php';
|
|
|
|
class Sequence extends VerySimpleModel {
|
|
|
|
static $meta = array(
|
|
'table' => SEQUENCE_TABLE,
|
|
'pk' => array('id'),
|
|
'ordering' => array('name'),
|
|
);
|
|
|
|
const FLAG_INTERNAL = 0x0001;
|
|
|
|
/**
|
|
* Function: next
|
|
*
|
|
* Fetch the next number in the sequence. The next number in the
|
|
* sequence will be adjusted in the database so that subsequent calls to
|
|
* this function should never receive the same result.
|
|
*
|
|
* Optionally, a format specification can be sent to the function and
|
|
* the next sequence number will be returned padded. See the `::format`
|
|
* function for more details.
|
|
*
|
|
* Optionally, a check callback can be specified to ensure the next
|
|
* value of the sequence is valid. This might be useful for a
|
|
* pseudo-random generator which might repeat existing numbers. The
|
|
* callback should have the following signature and should return
|
|
* boolean TRUE to approve the number.
|
|
*
|
|
* Parameters:
|
|
* $format - (string) Format specification for the result
|
|
* $check - (function($format, $next)) Validation callback function
|
|
* where $next will be the next value as an integer, and $formatted
|
|
* will be the formatted version of the number, if a $format
|
|
* parameter were passed to the `::next` method.
|
|
*
|
|
* Returns:
|
|
* (int|string) - next number in the sequence, optionally formatted and
|
|
* verified.
|
|
*/
|
|
function next($format=false, $check=false) {
|
|
$digits = $format ? $this->getDigitCount($format) : false;
|
|
|
|
if ($check && !is_callable($check))
|
|
$check = false;
|
|
|
|
do {
|
|
$next = $this->__next($digits);
|
|
$formatted = $format ? $this->format($format, $next) : $next;
|
|
}
|
|
while ($check
|
|
&& !call_user_func_array($check, array($formatted, $next)));
|
|
|
|
return $formatted;
|
|
}
|
|
|
|
/**
|
|
* Function: current
|
|
*
|
|
* Peeks at the next number in the sequence without incrementing the
|
|
* sequence.
|
|
*
|
|
* Parameters:
|
|
* $format - (string:optional) format string to receive the current
|
|
* sequence number
|
|
*
|
|
* Returns:
|
|
* (int|string) - the next number in the sequence without advancing the
|
|
* sequence, optionally formatted. See the `::format` method for
|
|
* formatting details.
|
|
*/
|
|
function current($format=false) {
|
|
return $format ? $this->format($format, $this->next) : $this->next;
|
|
}
|
|
|
|
/**
|
|
* Function: format
|
|
*
|
|
* Formats a number to the given format. The number will be placed into
|
|
* the format string according to the locations of hash characters (#)
|
|
* in the string. If more hash characters are encountered than digits
|
|
* the digits are left-padded accoring to the sequence padding
|
|
* character. If fewer are found, the last group will receive all the
|
|
* remaining digits.
|
|
*
|
|
* Hash characters can be escaped with a backslash (\#) and will emit a
|
|
* single hash character to the output.
|
|
*
|
|
* Parameters:
|
|
* $format - (string) Format string for the number, e.g. "TX-######-US"
|
|
* $number - (int) Number to appear in the format. If not
|
|
* specified the next number in this sequence will be used.
|
|
*/
|
|
function format($format, $number) {
|
|
$groups = array();
|
|
preg_match_all('/(?<!\\\)#+/', $format, $groups, PREG_OFFSET_CAPTURE);
|
|
|
|
$total = 0;
|
|
foreach ($groups[0] as $g)
|
|
$total += strlen($g[0]);
|
|
|
|
$number = str_pad($number, $total, $this->padding, STR_PAD_LEFT);
|
|
$output = '';
|
|
$start = $noff = 0;
|
|
// Interate through the ### groups and replace the number of hash
|
|
// marks with numbers from the sequence
|
|
foreach ($groups[0] as $g) {
|
|
$size = strlen($g[0]);
|
|
// Add format string from previous marker to current ## group
|
|
$output .= str_replace('\#', '#',
|
|
substr($format, $start, $g[1] - $start));
|
|
// Add digits from the sequence number
|
|
$output .= substr($number, $noff, $size);
|
|
// Set offset counts for the next loop
|
|
$start = $g[1] + $size;
|
|
$noff += $size;
|
|
}
|
|
// If there are more digits of number than # marks, add the number
|
|
// where the last hash mark was found
|
|
if (strlen($number) > $noff)
|
|
$output .= substr($number, $noff);
|
|
// Add format string from ending ## group
|
|
$output .= str_replace('\#', '#', substr($format, $start));
|
|
return $output;
|
|
}
|
|
|
|
function getDigitCount($format) {
|
|
$total = 0;
|
|
$groups = array();
|
|
|
|
return preg_match_all('/(?<!\\\)#/', $format, $groups);
|
|
}
|
|
|
|
/**
|
|
* Function: __next
|
|
*
|
|
* Internal implementation of the next number generator. This method
|
|
* will lock the database object backing to protect against concurent
|
|
* ticket processing. The lock will be released at the conclusion of the
|
|
* session.
|
|
*
|
|
* Parameters:
|
|
* $digits - (int:optional) number of digits (size) of the number. This
|
|
* is useful for random sequences which need a size hint to
|
|
* generate a "next" value.
|
|
*
|
|
* Returns:
|
|
* (int) - The current number in the sequence. The sequence is advanced
|
|
* and assured to be session-wise atomic before the value is returned.
|
|
*/
|
|
function __next($digits=false) {
|
|
// Ensure this block is executed in a single transaction
|
|
db_autocommit(false);
|
|
|
|
// Lock the database object -- this is important to handle concurrent
|
|
// requests for new numbers
|
|
static::objects()->filter(array('id'=>$this->id))->lock()->one();
|
|
|
|
// Increment the counter
|
|
$next = $this->next;
|
|
$this->next += $this->increment;
|
|
$this->updated = SqlFunction::NOW();
|
|
$this->save();
|
|
|
|
db_autocommit(true);
|
|
|
|
return $next;
|
|
}
|
|
|
|
function hasFlag($flag) {
|
|
return $this->flags & $flag != 0;
|
|
}
|
|
function setFlag($flag, $value=true) {
|
|
if ($value)
|
|
$this->flags |= $flag;
|
|
else
|
|
$this->flags &= ~$flag;
|
|
}
|
|
|
|
function getName() {
|
|
return $this->name;
|
|
}
|
|
|
|
function isValid() {
|
|
if (!$this->name)
|
|
return 'Name is required';
|
|
if (!$this->increment)
|
|
return 'Non-zero increment is required';
|
|
if (!$this->next || $this->next < 0)
|
|
return 'Positive "next" value is required';
|
|
|
|
if (!$this->padding)
|
|
$this->padding = '0';
|
|
|
|
return true;
|
|
}
|
|
|
|
function __get($what) {
|
|
// Pseudo-property for $sequence->current
|
|
if ($what == 'current')
|
|
return $this->current();
|
|
return parent::__get($what);
|
|
}
|
|
|
|
function __create($data) {
|
|
$instance = new self($data);
|
|
$instance->save();
|
|
return $instance;
|
|
}
|
|
}
|
|
|
|
class RandomSequence extends Sequence {
|
|
var $padding = '0';
|
|
|
|
function __next($digits=6) {
|
|
if ($digits < 6)
|
|
$digits = 6;
|
|
|
|
return Misc::randNumber($digits);
|
|
}
|
|
|
|
function current($format=false) {
|
|
return $this->next($format);
|
|
}
|
|
|
|
function save($refetch=false) {
|
|
throw new RuntimeException('RandomSequence is not database-backed');
|
|
}
|
|
}
|