Skip to content

Instantly share code, notes, and snippets.

@ratiw
Last active April 25, 2022 10:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ratiw/e15d643417039dd2d04b to your computer and use it in GitHub Desktop.
Save ratiw/e15d643417039dd2d04b to your computer and use it in GitHub Desktop.
Make new running number (e.g. doc no., invoice no.) based on the given pattern and the last number.
<?php
namespace App\Services;
class InvalidPatternException extends \Exception
{
}
/**
* Pattern is enclosed in the open and close delimiter
* The default open delimter is {
* The default close delimiter is }
* E.g. {YYYY}
*
* Pattern can be easily added by creating public static function
* that begins with "replace_" and followed by the pattern name
* E.g. public static function replace_YYYY($option) {}
*
* Pattern can define optional option by adding colon (:) at the end
* of the pattern, followed by the option string. This option will be
* passed to the corresponding "replace_" function. The meaning of the
* option is totally depending on the replace function.
* E.g. {YYYY:thai}
*
* Available Patterns
* 9 = number
* YYYY = 4 digits year
* YY = 2 digits year
* MM = 2 digits month
* DD = 2 digits day
* HH = 2 digits hour
* NN = 2 digits minute
* SS = 2 digits second
*
* E.g. 'JB{YY}{MM}-{9999}'
* JB = not a pattern, will left in-place
* {YY} = will be replaced with 2 digits of the current year
* {MM} = will be replaced with 2 digits of the current month
* - = not a pattern, will left in-place
* {9999} = running number, will be replaced with the 0001
* or the next number if the $lastNo is given
*/
class RunningNumberGenerator
{
/**
* Generate next running number based on the given pattern and last no
* If the last no. is not given, it will start from 1
*
* @param $pattern
* @param null $lastNo
* @return string
* @throws InvalidPatternException
*/
public static function make($pattern, $lastNo = null)
{
$patterns = static::parsePattern($pattern);
$offset = 0;
$reset = false;
$newNo = [];
foreach ($patterns as $prop) {
if (!$prop['is_pattern']) {
$newNo[] = $prop['value'];
$offset += strlen($prop['value']);
continue;
}
$key = (string) $prop['key'];
$length = strlen($key);
$segment = $lastNo ? substr($lastNo, $offset, $length) : '0';
if ($key[0] == '9') {
$prop['value'] = $reset ? 0 : (int)$segment;
$newNo[] = str_pad((string) ($prop['value'] + 1), $length, '0', STR_PAD_LEFT);
} else {
if ($segment != $prop['value']) $reset = true;
$newNo[] = $prop['value'];
}
$offset += $length;
}
return implode('', $newNo);
}
/**
* Parse the given pattern into array.
* Pattern is denoted by the given openning and closing symbol
* the defaults are { and }
*
* @param $pattern
* @param string $open openning symbol for the pattern
* @param string $close closing symbol for the pattern
* @return array
* @throws InvalidPatternException
*/
public static function parsePattern($pattern, $open = '{', $close = '}')
{
$patterns = [];
$other = '';
for ($i = 0; $i < strlen($pattern); $i++) {
if ($pattern[$i] == $open) {
if (!empty($other)) {
$patterns[] = ['key' => $other, 'is_pattern' => false, 'value' => $other];
$other = '';
}
if ($j = strpos($pattern, $close, $i + 1)) {
$key = substr($pattern, $i + 1, $j - $i - 1);
$option = explode(':', $key);
$key = array_shift($option);
$patterns[] = ['key' => $key, 'is_pattern' => true, 'value' => static::patternReplace($key, $option)];
$i = $j;
} else {
throw new InvalidPatternException("No close symbol: $close");
}
} else {
$other .= $pattern[$i];
}
}
return $patterns;
}
/**
* Replace the pattern with the corresponding value.
* More replacers can be added by extending the class and define
* replacer function with the function name starts with 'replace_'
* followed by the pattern itself.
*
* @param $pattern
* @param $option
* @return string
* @throws InvalidPatternException
*/
public static function patternReplace($pattern, $option)
{
if ($pattern[0] == '9') {
return str_repeat('0', strlen($pattern));
}
$replacer = 'replace_' . $pattern;
if (method_exists(__CLASS__, $replacer)) {
return static::$replacer($option);
}
throw new InvalidPatternException("No replacer for pattern {$pattern}");
}
protected static function replace_YYYY($option)
{
if (!empty($option) && $option[0] == 'thai') {
return (string) ((int) date('Y') + 543);
}
return date('Y');
}
protected static function replace_YY($option)
{
if (!empty($option) && $option[0] == 'thai') {
return substr((string) ((int) date('y') + 543), -2);
}
return date('y');
}
protected static function replace_MM($option)
{
return date('m');
}
protected static function replace_DD($option)
{
return date('d');
}
protected static function replace_HH($option)
{
return date('H');
}
protected static function replace_NN($option)
{
return date('i');
}
protected static function replace_SS($option)
{
return date('s');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment