PHP: Best way to Generate an unique numeric ID - php

I need to generate unique numeric id.
I could use uniqid, but it generates alphanumeric value.
And again I could use time, but there is no guarantee that it
will be unique always.
And again I could use the auto increment property of a field in a
database to get an unique id, but here we must need a database.
So what can be the best way to generate an unique numeric id?

Nothing can guarantee 100% uniqueness.
You need to know uniqueness comparing with what do you need.
And use any algorythm plus check each value in list of all used values.
In the world of programming what you need is called pseudo random number. So it's name actually explains what I mean.

Database systems use exclusive locking when creating numbers such as MySQL's auto_increment which takes care of concurrency and many other intricate details.
You have to approach the problem you have the same way - acquire a lock from the PHP process that's serving the request, look up the current value within some sort of persistent storage, increment it by 1, return it and release the lock.
The easiest way to do this is to use a good old file and exclusive locking.
I'll illustrate with a class (which should be debugged since it's not complete):
class MyAutoIncrement
{
protected $fh = null;
protected $file_path = '';
protected $auto_increment_offset = 1;
public function __construct($file_path, $offset = 1)
{
$this->file_path = $file_path;
$this->auto_increment_offset = $offset;
}
public function autoincrement()
{
if($this->acquire())
{
$current = (int)fread($this->fh);
$next += $this->auto_increment_offset;
fwrite($this->fh, $next);
$this->release();
return $next;
}
return null;
}
public function acquire()
{
$handler = $this->getFileHandler();
return flock($handler, LOCK_EX);
}
public function release($close = false)
{
$handler = $this->getFileHandler();
return flock($handler, LOCK_UN);
if($close)
{
fclose($handler);
$this->fh = null;
}
}
protected function acquireLock($handler)
{
return flock($handler, LOCK_EX);
}
protected function getFileHandler()
{
if(is_null($this->fh))
{
$this->fh = fopen($this->file_path, 'c+');
if($this->fh === false)
{
throw new \Exception(sprintf("Unable to open the specified file: %s", $this->file_path));
}
}
return $this->fh;
}
}
Usage:
$ai = new MyAutoIncrement('/path/to/counter/file.txt');
try
{
$id = $ai->autoincrement();
if(!is_null($id))
{
// Voila, you got your number, do stuff
}
else
{
// We went wrong somewhere
}
}
catch(\Exception $e)
{
// Something went wrong
}

As mentioned before. Nothing can guarantee 100% uniqueness.
Although this will be fairly unique :)
$iUniqueNumber = crc32(uniqid());
See
uniqid and crc32 polynomial of a string.

You can use a combination of time() and getmypid() to get what you need - a numeric unique ID. You can have multiple php processes launched at a given time, but they will never have the same process ID at that given time (unless the server process counter overlaps in less than a second, which is virtually impossible when kernel.pid_max is set correctly).
<?
function getUniqueID() {
return time() . '.' . getmypid();
}
?>
That function will generate a unique ID per script execution per server. It will fail if you call it multiple times in the same script and expect it to return unique value every time. In that case you can define some static variable inside the function body to keep track of that.

You talked about time, what about microtime?
Even if you create two numbers in a row you'll get a diferent value. You'll need of course to play a little around to make it an unique integer, but it should be able to help.

I suggest to concatenate PHP process ID with microtime(true) to increase possibility of having unique value.

function getserial()
{
$fn='/where_you_want/serial.dat';
$fp = fopen($fn, "r+");
if (flock($fp, LOCK_EX)) { $serial=fgets($fp);$serial++; }
else
{ print('lock error, ABORT'); exit; }
$h=fopen($fn.'.tmp','w'); fwrite($h,$serial);fclose($h);
if (filesize($fn.'.tmp')>0)
{
system('rm -f '.$fn.'.tmp');
fseek ($fp, 0);
fwrite($fp,$serial);
}
flock($fp, LOCK_UN); fclose($fp); #chmod($fn,0777);
return $serial;
}
this example will get you a unique serial number, after this, you can be sure it's existing with only one instance.
please note, to avoid data corruption, you must create first your file and put a number first.
(for example, write 1 without enter or anything else)
this is a really simple function, but it's working for me over 10 years now ...

You can make your own increment value to guarantee 100% uniqueness without use heavy algo:
Session unique id :
session_start();
$_SESSION['increment'] = 5;//i fix 5 but you need to get it in bdd,xml,...
function get_new_id(){
$_SESSION['increment']++;
//store new value of increment
return $_SESSION['increment'];
}
$my_unique_id = get_new_id();
echo $my_unique_id;
Global unique id (dont use this !):
function get_new_id(){
$increment = file_get_contents('increment.txt');
$increment++;
file_put_contents('increment.txt', $increment);
return $increment;
}
$my_unique_id = get_new_id();
echo $my_unique_id;

Related

Convert a string to function (callable) and keep it cached

I'm trying to make a little benchmarking script where I can enter short pieces of code for quick evaluation of my anticipations. I imagine it similar to jsPerf (but password-protected for security reasons).
The main loop should look like this:
public function run(&$t, $count) {
//Run setup function
if(is_callable($this->setup))
call_user_func($this->setup);
//Save inital time
$t($this->name);
//THE MAIN LOOP
for($i=0; $i<$count; $i++) {
call_user_func($this->fn);
}
//Save end time
$t($this->name."_end");
//return time difference
return $t[$this->name."-".$this->name."_end"];
}
However, this will only work with static approach - with functions defined while making the script:
//New instance of tester
$b = new Benchmarker();
$b->add(
//Name
"touch",
//closure
function() {
touch("file.txt");
},
//Code seen in final reports
"touch()"
);
So as you see, I use call_user_func, not eval. Besides the fact that it's evil function in it's nature, I want to avoid it for performance reasons. If I'm testing a code that takes about 10ns to process and eviluation takes about 100ns, my results will be rather random.
This is why I'm looking for a way to convert string to a callable object. You can think about it like one-time eval.
$callable = string_to_callable("function() {echo \"Hello world!\";}");
$b->add(
//Name
"echo",
//callable object
$callable,
//Code seen in final reports
"echo \"...\""
);
Is that possible?
Note:
I can see funny workaround using include:
//Code received from the user
$code = "echo \"Hello world!\";";
//Random name for a new function
$rndname = "fn_".rand(0,100000); //There are smarter ways to do this of course
//String of the new function
$func = "function $rndname() {{$code}}";
//Define a filename
$f = $rndname.".php";
//Put the code in the file
file_put_contents($f, "<?php\n$func\n?".">");
//Include the new script
include $f;
//Call the function
call_user_func($rndname);
//Delete the file
unlink($f);
I really do hope that I won't need the code above!
Apart from creating a new file, there may be a closure trick:
function string_to_callable($string) {
return eval("return function() {{$string}};");
}

Laravel : How do I compare/obtain specific datas from cache?

In my normal polling Laravel chat app, I will save new messages sent by a user into a file cache with the key as a string, getting its value from date(current_time) function and the body of the message.
Then, when I want to obtain those messages, I will use the last Poll value $lastPolled = Session::get('lastPolled') and compare with the key in the cache. Keys that are greater than the $lastPolled value will have their data to be taken as new messages and appended into the conversations.
Finally, I will update the last polled session value Session::put('lastPolled',date(Y-m-d H:i:s)
So, how do I compare $lastPolled with all the keys in cache and get each key's values? Something along the lines of:
$latestMessages = array();
foreach(KeysInCache as Key=>value){
if($lastPolled>Key)
array_push($latestMessages,Key=>value);
}
Thank you!
P.s. bonus points for better suggestions. Oh and I can't use memcache/redis/otherSuperCaches for technical reasons, only file/database cache. :(
Why not try some thing like this by creating cache files based on timestamp or key :
Further details and suggestions on same at : http://evertpot.com/107/
// This is the function you store information with function
store($key,$data,$ttl) {
// Opening the file
$h = fopen($this->getFileName($key),'w');
if (!$h) throw new Exception('Could not write to cache');
// Serializing along with the TTL
$data = serialize(array(time()+$ttl,$data));
if (fwrite($h,$data)===false) {
throw new Exception('Could not write to cache');
}
fclose($h);
}
// General function to find the filename for a certain key private
function getFileName($key) {
return '/tmp/s_cache' . md5($key);
}
// The function to fetch data returns false on failure function
fetch($key) {
$filename = $this->getFileName($key);
if (!file_exists($filename) || !is_readable($filename)) return false;
$data = file_get_contents($filename);
$data = #unserialize($data);
if (!$data) {
// Unlinking the file when unserializing failed
unlink($filename);
return false;
}
// checking if the data was expired
if (time() > $data[0]) {
// Unlinking
unlink($filename);
return false;
}
return $data[1];
}
}

PHP APC problems when storing objects

I am trying to build an configuration parser for my application I installed APC today, but everytime I try to put an serialized object in the store, it does not get in there and does not. (I am checking with apc.php for my version[3.1.8-dev] on PHP 5.3.16 [My Dev Environment], so I am sure that the data is not in the cache). this is how I pass the data to the cacher:
// The data before the caching
array (
'key' => md5($this->filename),
'value' => serialize($this->cfg)
);
// The caching interface
function($argc){
$key = $argc['key'];
Cache\APC::getInstance()->set($key,$argc['value']);
}
// The caching method described above
public function set($key, $val) {
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
else
return false;
}
// the constructor of the configuration class.
// It 1st looks for the configuration in
// the cache if it is not present performs the reading from the file.
public function __construct($filename = '/application/config/application.ini',
$type = self::CONFIG_INI)
{
if (defined('SYSTEM_CACHE') && SYSTEM_CACHE === 'APC'){
$key = md5($filename);
$cfg = APC::getInstance()->get($key);
if (!empty($cfg)) {
print "From Cache";
$this->cfg = unserialize($cfg);
return;
} else {
print "From File";
}
}
}
I did a few tests and there is not a problem with the MD5() key (which I thought while writing this question) nor with APC itself. I am really stuck on this one, nothing odd in the logs, so if anyone can give me at least some directions will be very appreciated.
Thanks in advance!
The problem is was in my code:\
public function set($key, $val) {
/*
*
* If the key exists in the cache delete it and store it again,
* but how could it exist when the else clause is like that...
*/
if (apc_exists($key)) {
apc_delete ($key);
return apc_store($key, $val);
}
// This is very wrong in the current case
// cuz the function will never store and the if will
// never match..
else
return false;
}
NOTE:
Always think and keep your eyes open, if you still can't find anything get off the PC and give yourself a rest. Get back after 10-15 minutes and pown the code. It helps! :D

How to lock file in PHP?

I'm trying to create a PHP file, which wouldn't run if it's already running. Here's the code I'm using:
<?php
class Test {
private $tmpfile;
public function action_run() {
$this->die_if_running();
$this->run();
}
private function die_if_running() {
$this->tmpfile = #fopen('.refresher2.pid', "w");
$locked = #flock($this->tmpfile, LOCK_EX|LOCK_NB);
if (! $locked) {
#fclose($this->tmpfile);
die("Running 2");
}
}
private function run() {
echo "NOT RUNNNING";
sleep(100);
}
}
$test = new Test();
$test->action_run();
The problem is, when I run this from console, it works great. But when I try to run it from browser, many instances can run simultaneously. This is on Windows 7, XAMPP, PHP 5.3.2. I guess OS is thinking that it's the same process and thus the functionality falls. Is there a cross-platform way to create a PHP script of this type?
Not really anything to promising. You can't use flock for that like this.
You could use system() to start another (php) process that does the locking for you. But drawbacks:
You need to do interprocess communication. Think about a way how to tell the other program when to release the lock etc. You can use stdin for messenging und use 3 constants or something. In this case it's still rather simple
It's bad for performance because you keep creating processes which is expensive.
Another way would be to start another program that runs all the time. You connect to it using some means of IPC (probably just use a tcp channel because it's cross-platform) and allow this program to manage file acces. That program could be a php script in an endless loop as well, but it will probably be simpler to code this in Java or another language that has multithreading support.
Another way would be to leverage existing ressources. Create a dummy database table for locks, create an entry for the file and then do table-row-locking.
Another way would be not to use files, but a database.
I had a similar problem a while ago.
I needed to have a counter where the number returned was unique.
I used a lock-file and only if this instance was able to create the lock-file was it allowed to read the file with the current number.
Instead of counting up perhaps you can allow the script to run.
The trick is to let try a few times (like 5) with a small wait/sleep in between.
function GetNextNumber()
{
$lockFile = "lockFile.txt";
$lfh = #fopen($lockFile, "x");
if (!$lfh)
{
$lockOkay = false;
$count = 0;
$countMax = 5;
// Try ones every second in 5 seconds
while (!$lockOkay & $count < $countMax)
{
$lfh = #fopen($lockFile, "x");
if ($lfh)
{
$lockOkay = true;
}
else
{
$count++;
sleep(1);
}
}
}
if ($lfh)
{
$fh = fopen($myFile, 'r+') or die("Too many users. ");
flock($fh, LOCK_EX);
$O_nextNumber = fread($fh, 15);
$O_nextNumber = $O_nextNumber + 1;
rewind($fh);
fwrite($fh, $O_knr);
flock($fh, LOCK_UN);
fclose($fh);
unlink($lockFile); // Sletter lockfilen
}
return $O_nextNumber;
}

best way to obtain a lock in php

I'm trying to update a variable in APC, and will be many processes trying to do that.
APC doesn't provide locking functionality, so I'm considering using other mechanisms... what I've found so far is mysql's GET_LOCK(), and php's flock(). Anything else worth considering?
Update: I've found sem_acquire, but it seems to be a blocking lock.
/*
CLASS ExclusiveLock
Description
==================================================================
This is a pseudo implementation of mutex since php does not have
any thread synchronization objects
This class uses flock() as a base to provide locking functionality.
Lock will be released in following cases
1 - user calls unlock
2 - when this lock object gets deleted
3 - when request or script ends
==================================================================
Usage:
//get the lock
$lock = new ExclusiveLock( "mylock" );
//lock
if( $lock->lock( ) == FALSE )
error("Locking failed");
//--
//Do your work here
//--
//unlock
$lock->unlock();
===================================================================
*/
class ExclusiveLock
{
protected $key = null; //user given value
protected $file = null; //resource to lock
protected $own = FALSE; //have we locked resource
function __construct( $key )
{
$this->key = $key;
//create a new resource or get exisitng with same key
$this->file = fopen("$key.lockfile", 'w+');
}
function __destruct()
{
if( $this->own == TRUE )
$this->unlock( );
}
function lock( )
{
if( !flock($this->file, LOCK_EX | LOCK_NB))
{ //failed
$key = $this->key;
error_log("ExclusiveLock::acquire_lock FAILED to acquire lock [$key]");
return FALSE;
}
ftruncate($this->file, 0); // truncate file
//write something to just help debugging
fwrite( $this->file, "Locked\n");
fflush( $this->file );
$this->own = TRUE;
return TRUE; // success
}
function unlock( )
{
$key = $this->key;
if( $this->own == TRUE )
{
if( !flock($this->file, LOCK_UN) )
{ //failed
error_log("ExclusiveLock::lock FAILED to release lock [$key]");
return FALSE;
}
ftruncate($this->file, 0); // truncate file
//write something to just help debugging
fwrite( $this->file, "Unlocked\n");
fflush( $this->file );
$this->own = FALSE;
}
else
{
error_log("ExclusiveLock::unlock called on [$key] but its not acquired by caller");
}
return TRUE; // success
}
};
You can use the apc_add function to achieve this without resorting to file systems or mysql. apc_add only succeeds when the variable is not already stored; thus, providing a mechanism of locking. TTL can be used to ensure that falied lockholders won't keep on holding the lock forever.
The reason apc_add is the correct solution is because it avoids the race condition that would otherwise exist between checking the lock and setting it to 'locked by you'. Since apc_add only sets the value if it's not already set ( "adds" it to the cache ), it ensures that the lock can't be aquired by two calls at once, regardless of their proximity in time. No solution that doesn't check and set the lock at the same time will inherently suffer from this race condition; one atomic operation is required to successfully lock without race condition.
Since APC locks will only exist in the context of that php execution, it's probably not the best solution for general locking, as it doesn't support locks between hosts. Memcache also provides an atomic add function and thus can also be used with this technique - which is one method of locking between hosts. Redis also supports atomic 'SETNX' functions and TTL, and is a very common method of locking and synchronization between hosts. Howerver, the OP requests a solution for APC in particular.
If the point of the lock is to prevent multiple processes from trying to populate an empty cache key, why wouldn't you want to have a blocking lock?
$value = apc_fetch($KEY);
if ($value === FALSE) {
shm_acquire($SEMAPHORE);
$recheck_value = apc_fetch($KEY);
if ($recheck_value !== FALSE) {
$new_value = expensive_operation();
apc_store($KEY, $new_value);
$value = $new_value;
} else {
$value = $recheck_value;
}
shm_release($SEMAPHORE);
}
If the cache is good, you just roll with it. If there's nothing in the cache, you get a lock. Once you have the lock, you'll need to double-check the cache to make sure that, while you were waiting to get the lock, the cache wasn't repopulated. If the cache was repopulated, use that value & release the lock, otherwise, you do the computation, populate the cache & then release your lock.
Actually, check to see if this will work better then Peter's suggestion.
http://us2.php.net/flock
use an exclusive lock and if your comfortable with it, put everything else that attempted to lock the file in a 2-3 second sleep. If done right your site will experience a hang regarding the locked resource but not a horde of scripts fighting to cache the samething.
If you don't mind basing your lock on the filesystem, then you could use fopen() with mode 'x'. Here is an example:
$f = fopen("lockFile.txt", 'x');
if($f) {
$me = getmypid();
$now = date('Y-m-d H:i:s');
fwrite($f, "Locked by $me at $now\n");
fclose($f);
doStuffInLock();
unlink("lockFile.txt"); // unlock
}
else {
echo "File is locked: " . file_get_contents("lockFile.txt");
exit;
}
See www.php.net/fopen
I realize this is a year old, but I just stumbled upon the question while doing some research myself on locking in PHP.
It occurs to me that a solution might be possible using APC itself. Call me crazy, but this might be a workable approach:
function acquire_lock($key, $expire=60) {
if (is_locked($key)) {
return null;
}
return apc_store($key, true, $expire);
}
function release_lock($key) {
if (!is_locked($key)) {
return null;
}
return apc_delete($key);
}
function is_locked($key) {
return apc_fetch($key);
}
// example use
if (acquire_lock("foo")) {
do_something_that_requires_a_lock();
release_lock("foo");
}
In practice I might throw another function in there to generate a key to use here, just to prevent collision with an existing APC key, e.g.:
function key_for_lock($str) {
return md5($str."locked");
}
The $expire parameter is a nice feature of APC to use, since it prevents your lock from being held forever if your script dies or something like that.
Hopefully this answer is helpful for anyone else who stumbles here a year later.
EAccelerator has methods for it; eaccelerator_lock and eaccelerator_unlock.
Can't say if this is the best way to handle the job, but at least it is convenient.
function WhileLocked($pathname, callable $function, $proj = ' ')
{
// create a semaphore for a given pathname and optional project id
$semaphore = sem_get(ftok($pathname, $proj)); // see ftok for details
sem_acquire($semaphore);
try {
// capture result
$result = call_user_func($function);
} catch (Exception $e) {
// release lock and pass on all errors
sem_release($semaphore);
throw $e;
}
// also release lock if all is good
sem_release($semaphore);
return $result;
}
Usage is as simple as this.
$result = WhileLocked(__FILE__, function () use ($that) {
$this->doSomethingNonsimultaneously($that->getFoo());
});
Third optional argument can come handy if you use this function more than once per file.
Last but not least it isn't hard to modify this function (while keeping its signature) to use any other kind of locking mechanism at a later date, e.g. if you happen to find yourself working with multiple servers.
APC is now considered unmaintained and dead. It's successor APCu offers locking via apcu_entry. But be aware, that it also prohibits the concurrent execution of any other APCu functions. Depending on your use case, this might be OK for you.
From the manual:
Note: When control enters apcu_entry() the lock for the cache is acquired exclusively, it is released when control leaves apcu_entry(): In effect, this turns the body of generator into a critical section, disallowing two processes from executing the same code paths concurrently. In addition, it prohibits the concurrent execution of any other APCu functions, since they will acquire the same lock.
APCu has apcu_entry since 5.1.0, can implement a lock mechanism with it now:
/** get a lock, will wait until the lock is available,
* make sure handle deadlock yourself :p
*
* useage : $lock = lock('THE_LOCK_KEY', uniqid(), 50);
*
* #param $lock_key : the lock you want to get it
* #param $lock_value : the unique value to specify lock owner
* #param $retry_millis : wait befor retry
* #return ['lock_key'=>$lock_key, 'lock_value'=>$lock_value]
*/
function lock($lock_key, $lock_value, $retry_millis) {
$got_lock = false;
while (!$got_lock) {
$fetched_lock_value = apcu_entry($lock_key, function ($key) use ($lock_value) {
return $lock_value;
}, 100);
$got_lock = ($fetched_lock_value == $lock_value);
if (!$got_lock) usleep($retry_millis*1000);
}
return ['lock_key'=>$lock_key, 'lock_value'=>$lock_value];
}
/** release a lock
*
* usage : unlock($lock);
*
* #param $lock : return value of function lock
*/
function unlock($lock) {
apcu_delete($lock['lock_key']);
}
What I've found, actually, is that I don't need any locking at all... given what I'm trying to create is a map of all the class => path associations for autoload, it doesn't matter if one process overwrites what the other one has found (it's highly unlikely, if coded properly), because the data will get there eventually anyway. So, the solution turned out to be "no locks".

Categories