Skip to content

Commit b72447e

Browse files
committed
Port of the javascript ciud class to php
0 parents  commit b72447e

9 files changed

+423
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
composer.lock
2+
phpunit.xml
3+
vendor

.travis.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
php:
2+
- 5.4
3+
- 5.5
4+
- 5.6
5+
- hhvm
6+
7+
sudo: false
8+
9+
before_script: travis_retry composer install --no-interaction --prefer-source
10+
11+
script: vendor/bin/phpunit

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Joseph Cohen <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Shoperti Ciud

composer.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "shoperti/ciud",
3+
"description": "Collision-resistant ids optimized for horizontal scaling and performance.",
4+
"license": "MIT",
5+
"keywords": [
6+
"ciud",
7+
"shoperti",
8+
"php"
9+
],
10+
"require": {
11+
"php": ">=5.4.0"
12+
},
13+
"require-dev": {
14+
"phpunit/phpunit": "~4.0"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"Shoperti\\Ciud\\": "src/"
19+
}
20+
},
21+
"autoload-dev": {
22+
"psr-4": {
23+
"Shoperti\\Tests\\Ciud\\": "tests/"
24+
}
25+
},
26+
"extra": {
27+
"branch-alias": {
28+
"dev-master": "1.0-dev"
29+
}
30+
},
31+
"minimum-stability": "dev",
32+
"prefer-stable": true
33+
}

phpunit.xml.dist

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit backupGlobals="false"
3+
backupStaticAttributes="false"
4+
beStrictAboutTestsThatDoNotTestAnything="true"
5+
beStrictAboutOutputDuringTests="true"
6+
bootstrap="vendor/autoload.php"
7+
colors="true"
8+
convertErrorsToExceptions="true"
9+
convertNoticesToExceptions="true"
10+
convertWarningsToExceptions="true"
11+
processIsolation="false"
12+
stopOnError="false"
13+
stopOnFailure="false"
14+
verbose="true"
15+
>
16+
<testsuites>
17+
<testsuite name="Shoperti Ciud Test Suite">
18+
<directory suffix="Test.php">./tests</directory>
19+
</testsuite>
20+
</testsuites>
21+
<filter>
22+
<whitelist processUncoveredFilesFromWhitelist="true">
23+
<directory suffix=".php">./src</directory>
24+
</whitelist>
25+
</filter>
26+
</phpunit>

src/Ciud.php

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Shoperti.
5+
*
6+
* (c) Joseph Cohen <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Shoperti\Ciud;
13+
14+
/**
15+
* This is the Ciud class.
16+
*
17+
* @author Joseph Cohen <[email protected]>
18+
*/
19+
class Ciud
20+
{
21+
/**
22+
* The factory to use when creating UUIDs.
23+
*
24+
* @var \Shoperti\Ciud\CiudFactory
25+
*/
26+
private static $factory = null;
27+
28+
/**
29+
* Returns the currently set factory used to create Ciuds.
30+
*
31+
* @return \Shoperti\Ciud\CiudFactory
32+
*/
33+
public static function getFactory()
34+
{
35+
if (!self::$factory) {
36+
self::$factory = new CiudFactory();
37+
}
38+
39+
return self::$factory;
40+
}
41+
42+
/**
43+
* Generates a new ciud.
44+
*
45+
* @param string|null $prefix
46+
*
47+
* @return string
48+
*/
49+
public static function ciud($prefix = null)
50+
{
51+
return static::getFactory()->ciud($prefix);
52+
}
53+
54+
/**
55+
* Generates a new slug string.
56+
*
57+
* @return string
58+
*/
59+
public static function slug($prefix = null)
60+
{
61+
return static::getFactory()->slug($prefix);
62+
}
63+
}

src/CiudFactory.php

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Shoperti.
5+
*
6+
* (c) Joseph Cohen <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Shoperti\Ciud;
13+
14+
/**
15+
* This is the Ciud class.
16+
*
17+
* @author Joseph Cohen <[email protected]>
18+
*/
19+
class CiudFactory
20+
{
21+
/**
22+
* The base string constant.
23+
*
24+
* @return int
25+
*/
26+
const BASE = 36;
27+
28+
/**
29+
* The block size constant.
30+
*
31+
* @return int
32+
*/
33+
const BLOCK_SIZE = 4;
34+
35+
/**
36+
* The counter.
37+
*
38+
* @return int
39+
*/
40+
protected $counter = 0;
41+
42+
/**
43+
* Generates discrete values.
44+
*
45+
* @return string
46+
*/
47+
private $discreteValues;
48+
49+
/**
50+
* Creates a new ciud factory instance.
51+
*
52+
* @return void
53+
*/
54+
public function __construct()
55+
{
56+
$this->discreteValues = pow(self::BASE, self::BLOCK_SIZE);
57+
}
58+
59+
/**
60+
* Pads the input string into specific size
61+
*
62+
* @param string $str
63+
* @param int $size
64+
*
65+
* @return string
66+
*/
67+
protected function pad($str, $size)
68+
{
69+
$str = '000000000'.$str;
70+
71+
return substr($str, strlen($str) - $size);
72+
}
73+
74+
/**
75+
* Generates a random number.
76+
*
77+
* @return string
78+
*/
79+
protected function random()
80+
{
81+
return mt_rand() / mt_getrandmax();
82+
}
83+
84+
/**
85+
* Creates a random block.
86+
*
87+
* @return string
88+
*/
89+
protected function randomBlock()
90+
{
91+
return $this->pad(
92+
base_convert((int) ($this->random() * $this->discreteValues << 0), 10, self::BASE),
93+
self::BLOCK_SIZE
94+
);
95+
}
96+
97+
/**
98+
* Gets a safe counter.
99+
*
100+
* @return int
101+
*/
102+
protected function safeCounter()
103+
{
104+
$this->counter = ($this->counter < $this->discreteValues) ? $this->counter : 0;
105+
++$this->counter;
106+
return $this->counter - 1;
107+
}
108+
109+
/**
110+
* Gets a utf8 char code.
111+
*
112+
* @param string $str
113+
*
114+
* @return string
115+
*/
116+
protected function charCodeAt($str) {
117+
list(, $ord) = unpack('N', mb_convert_encoding($str, 'UCS-4BE', 'UTF-8'));
118+
return $ord;
119+
}
120+
121+
/**
122+
* Generates a unique fingerprint based on the host.
123+
*
124+
* @return string
125+
*/
126+
protected function fingerprint()
127+
{
128+
$padding = 2;
129+
130+
$pid = $this->pad(
131+
base_convert((int) getmypid(), 10, 36), $padding
132+
);
133+
134+
$hostname = gethostname();
135+
$length = strlen($hostname);
136+
137+
$hostId = $this->pad(
138+
base_convert((int) array_reduce(str_split($hostname), function ($prev, $char) {
139+
return +$prev + $this->charCodeAt($char);
140+
}, +$length + 36), 10, 36),
141+
$padding
142+
);
143+
144+
return $pid.$hostId;
145+
}
146+
147+
/**
148+
* Generates a new ciud.
149+
*
150+
* @param string|null $prefix
151+
*
152+
* @return string
153+
*/
154+
public function ciud($prefix = null)
155+
{
156+
// Starting with a lowercase letter makes
157+
// it HTML element ID friendly.
158+
$letter = $prefix ?: 'c'; // hard-coded allows for sequential access
159+
160+
// timestamp
161+
// warning: this exposes the exact date and time
162+
// that the uid was created.
163+
$timestamp = base_convert(substr(microtime(true)*1000, 0, 13), 10, self::BASE);
164+
165+
// A few chars to generate distinct ids for different
166+
// clients (so different computers are far less
167+
// likely to generate the same id)
168+
$fingerprint = $this->fingerprint();
169+
170+
// Grab some more chars from Math.random()
171+
$random = $this->randomBlock().$this->randomBlock();
172+
173+
// Prevent same-machine collisions.
174+
$counter = $this->pad(base_convert((int) $this->safeCounter(), 10, self::BASE), self::BLOCK_SIZE);
175+
176+
return ($letter.$timestamp.$counter.$fingerprint.$random);
177+
}
178+
179+
/**
180+
* Generates a new slug string.
181+
*
182+
* @return string
183+
*/
184+
public function slug()
185+
{
186+
$timestamp = base_convert(substr(microtime(true)*1000, 0, 13), 10, self::BASE);
187+
$counter = substr(base_convert((int) $this->safeCounter(), 10, self::BASE), -4);
188+
$fingerprint = substr($this->fingerprint(), 0 ,1).substr($this->fingerprint(), -1);
189+
$random = substr($this->randomBlock(), -2);
190+
return (substr($timestamp, -2).$counter.$fingerprint.$random);
191+
}
192+
}

0 commit comments

Comments
 (0)