Uuid.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <?php
  2. /**
  3. * Author: Tony Chen
  4. */
  5. namespace app\common\until;
  6. /**
  7. * @file
  8. * Provide basic v3, v4, and v5 UUID functionality for PHP.
  9. *
  10. * Created by Matt Farina on 2011-11-29.
  11. */
  12. use \Exception;
  13. /**
  14. * This class creates RFC 4122 compliant Universally Unique Identifiers (UUID).
  15. *
  16. * The generated UUIDs can be version 3, 4, or 5. Functionality is provided to
  17. * validate UUIDs as well as validate name based UUIDs.
  18. *
  19. * @see https://tools.ietf.org/html/rfc4122
  20. * @see https://en.wikipedia.org/wiki/UUID
  21. * @see https://github.com/lootils/uuid
  22. *
  23. * @author Matt Farina <matt@mattfarina.com>
  24. * @copyright MIT License
  25. * @version 1.0
  26. */
  27. class Uuid {
  28. /**
  29. * @var string DNS namespace from RFC 4122 appendix C.
  30. */
  31. const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
  32. /**
  33. * @var string URL namespace from RFC 4122 appendix C.
  34. */
  35. const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
  36. /**
  37. * @var string ISO OID namespace from RFC 4122 appendix C.
  38. */
  39. const OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
  40. /**
  41. * @var string X.500 namespace from RFC 4122 appendix C.
  42. */
  43. const X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
  44. /**
  45. * @var string NULL UUID string from RFC 4122.
  46. */
  47. const NIL = '00000000-0000-0000-0000-000000000000';
  48. /**
  49. * @var UUID Version 3.
  50. */
  51. const V3 = '3';
  52. /**
  53. * @var UUID Version 4.
  54. */
  55. const V4 = '4';
  56. /**
  57. * @var UUID Version 5.
  58. */
  59. const V5 = '5';
  60. /**
  61. * @var string the first 32 bits of the UUID.
  62. */
  63. protected $time_low = NULL;
  64. /**
  65. * @var string the next 16 bits of the UUID.
  66. */
  67. protected $time_mid = NULL;
  68. /**
  69. * @var string the next 16 bits of the UUID.
  70. */
  71. protected $time_hi_version = NULL;
  72. /**
  73. * @var string the next 8 bits of the UUID.
  74. */
  75. protected $clock_seq_hi_variant = NULL;
  76. /**
  77. * @var string the next 8 bits of the UUID.
  78. */
  79. protected $clock_seq_low = NULL;
  80. /**
  81. * @var string the last 48 bits of the UUID.
  82. */
  83. protected $node = NULL;
  84. /**
  85. * @var string the version of the UUID.
  86. */
  87. protected $version = NULL;
  88. /**
  89. * @var string the namespace used when the UUID was generated.
  90. */
  91. protected $namespace = NULL;
  92. /**
  93. * @var string the name used when the UUID was generated.
  94. */
  95. protected $name = NULL;
  96. /**
  97. * Return a list of UUID fields. These can be used with the getField() method.
  98. *
  99. * @return array
  100. * A list of fields on the UUID. These can be used with the getField method.
  101. */
  102. public function listFields() {
  103. return [
  104. 'time_low',
  105. 'time_mid',
  106. 'time_hi_version',
  107. 'clock_seq_hi_variant',
  108. 'clock_seq_low',
  109. 'node',
  110. ];
  111. }
  112. /**
  113. * Get the value of a UUID field.
  114. *
  115. * @param string $name
  116. * The name of a field to get the vurrent value. The values are returned in
  117. * hex format.
  118. */
  119. public function getField($name) {
  120. if (!in_array($name, $this->listFields())) {
  121. throw new Exception('A field value was requested for an invalid field name.');
  122. }
  123. return $this->$name;
  124. }
  125. /**
  126. * Get the UUID version for this UUID.
  127. *
  128. * @return string
  129. * The UUID Version number.
  130. */
  131. public function getVersion() {
  132. return $this->version;
  133. }
  134. /**
  135. * Get the UUID namespace for this UUID.
  136. *
  137. * @return string
  138. * The UUID namespace if known.
  139. */
  140. public function getNamespace() {
  141. return $this->namespace;
  142. }
  143. /**
  144. * Get the UUID name for this UUID.
  145. *
  146. * @return string
  147. * The UUID name if known.
  148. */
  149. public function getName() {
  150. return $this->name;
  151. }
  152. /**
  153. * Set the version of the UUID.
  154. *
  155. * @param string $version
  156. * 3, 4, or 5 which are the possible suppored versions.
  157. */
  158. protected function setVersion($version) {
  159. if ($version == self::V3 || $version == self::V4 || $version == self::V5) {
  160. $this->version = $version;
  161. } else {
  162. throw new Exception('An invalid UUID version was specified.');
  163. }
  164. }
  165. /**
  166. * Get the URN for a URI.
  167. */
  168. public function getURN() {
  169. return 'urn:uuid:' . $this;
  170. }
  171. /**
  172. * Get the UUID.
  173. *
  174. * @return string
  175. * A string containing a properly formatted UUID.
  176. */
  177. public function getUuid() {
  178. return $this->time_low . '-' . $this->time_mid . '-' . $this->time_hi_version . '-' . $this->clock_seq_hi_variant . $this->clock_seq_low . '-' . $this->node;
  179. }
  180. /**
  181. * Construct a new UUID object.
  182. *
  183. * There are a number of ways this UUID object could be generated.
  184. * - By the hex UUID: '{12345678-1234-5678-1234-567812345678}'
  185. * - Via the fields passed in as an array. For a list of fields see the method
  186. * listFields().
  187. * - From a URN: 'urn:uuid:12345678-1234-5678-1234-567812345678'
  188. *
  189. * @param mixed $uuid
  190. * The UUID in a format that can be parsed.
  191. * @param string $version const
  192. * (optional but recommended) The UUID version. If none specified we simply don't know it.
  193. * @param string $namespace
  194. * (optional) The namespace used when the UUID was generated. This is useful with v3 and v5 UUIDs.
  195. * @param string $name
  196. * (optional) The name used when the UUID was generated. This is useful with v3 and v5 UUIDs.
  197. */
  198. public function __construct($uuid, $version = NULL, $namespace = NULL, $name = NULL) {
  199. $this->parse($uuid);
  200. if (!is_null($version)) {
  201. $this->setVersion($version);
  202. }
  203. $this->namespace = $namespace;
  204. $this->name = $name;
  205. }
  206. /**
  207. * Parse the UUID from the available formats.
  208. *
  209. * @todo this should be written prettier. For realz.
  210. */
  211. protected function parse($uuid) {
  212. // The UUID as a standard string was passed in.
  213. if (is_string($uuid)) {
  214. if (substr($uuid, 0, 1) === '{' && substr($uuid, -1, 1) === '}') {
  215. $string = substr($uuid, 1, strlen($uuid) - 2);
  216. $this->parseStringToParts($string);
  217. } // The case where a URL was supplied.
  218. else if (substr($uuid, 0, 9) === 'urn:uuid:') {
  219. $string = substr($uuid, 9);
  220. $this->parseStringToParts($string);
  221. } else {
  222. throw new Exception('The UUID string supplied could not be parsed.');
  223. }
  224. } else if (is_array($uuid)) {
  225. if (count($uuid) != 6) {
  226. throw new Exception('The UUID array supplied could not be parsed.');
  227. }
  228. // For the case where a UUID is passed in via the format:
  229. // array('35e872b4', '190a', '5faa', 'a0', 'f6', '09da0d4f9c01');
  230. if (isset($uuid[0]) && !empty($uuid[0])) {
  231. $this->time_low = $uuid[0];
  232. $this->time_mid = $uuid[1];
  233. $this->time_hi_version = $uuid[2];
  234. $this->clock_seq_hi_variant = $uuid[3];
  235. $this->clock_seq_low = $uuid[4];
  236. $this->node = $uuid[5];
  237. }
  238. // For the case where the UUID is passed in via the format:
  239. // array(
  240. // 'time_low' => '35e872b4',
  241. // 'time_mid' => '190a',
  242. // 'time_hi_version' => '5faa',
  243. // 'clock_seq_hi_variant' => 'a0',
  244. // 'clock_seq_low' => 'f6',
  245. // 'node' => '09da0d4f9c01',
  246. // );
  247. else if (isset($uuid['time_low']) && !empty($uuid['time_low'])) {
  248. $this->time_low = $uuid['time_low'];
  249. $this->time_mid = $uuid['time_mid'];
  250. $this->time_hi_version = $uuid['time_hi_version'];
  251. $this->clock_seq_hi_variant = $uuid['clock_seq_hi_variant'];
  252. $this->clock_seq_low = $uuid['clock_seq_low'];
  253. $this->node = $uuid['node'];
  254. } else {
  255. throw new Exception('The UUID array supplied could not be parsed.');
  256. }
  257. } else {
  258. throw new Exception('The UUID supplied could not be parsed.');
  259. }
  260. }
  261. /**
  262. * Parse a string in the form of 12345678-1234-5678-1234-567812345678.
  263. */
  264. protected function parseStringToParts($string) {
  265. $parts = explode('-', $string);
  266. if (count($parts) != 5) {
  267. throw new Exception('The UUID string supplied could not be parsed.');
  268. }
  269. foreach ($parts as $id => $part) {
  270. switch ($id) {
  271. case 0:
  272. $this->time_low = $part;
  273. break;
  274. case 1:
  275. $this->time_mid = $part;
  276. break;
  277. case 2:
  278. $this->time_hi_version = $part;
  279. break;
  280. case 3:
  281. $this->clock_seq_hi_variant = substr($part, 0, 2);
  282. $this->clock_seq_low = substr($part, 2);
  283. break;
  284. case 4:
  285. $this->node = $part;
  286. break;
  287. }
  288. }
  289. }
  290. /**
  291. * Display the UUID as a string in the format 6ba7b810-9dad-11d1-80b4-00c04fd430c8.
  292. */
  293. public function __toString() {
  294. return $this->getUuid();
  295. }
  296. /**
  297. * Validate if a UUID has a valid format.
  298. *
  299. * @param string $uuid
  300. * The string to validate if it is in the proper UUID format.
  301. *
  302. * @return bool TRUE if the format is valid and FALSE otherwise.
  303. */
  304. public static function isValid($uuid) {
  305. return (preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $uuid) === 1);
  306. }
  307. /**
  308. * Version 5 UUIDs are based on a namespace and name.
  309. *
  310. * If you have the same namespace and name you can recreate the namespace.
  311. * V5 UUIDs are prefered over v3. V5 is based on sha1 while v3 is based on md5.
  312. *
  313. * @see https://en.wikipedia.org/wiki/UUID#Version_5_.28SHA-1_hash.29
  314. *
  315. * @param string $namespace
  316. * The UUID of the given namespace.
  317. * @param string $name
  318. * The name we are creating the UUID for.
  319. */
  320. public static function createV5($namespace, $name) {
  321. // If the namespace is not a valid UUID we throw an error.
  322. if (!self::isValid($namespace)) {
  323. throw new Exception('The UUID provided for the namespace is not valid.');
  324. }
  325. $bin = self::bin($namespace);
  326. $hash = sha1($bin . $name);
  327. return new self (sprintf('{%08s-%04s-%04x-%04x-%12s}',
  328. // 32 bits for "time_low"
  329. substr($hash, 0, 8),
  330. // 16 bits for "time_mid"
  331. substr($hash, 8, 4),
  332. // 16 bits for "time_hi_and_version",
  333. // four most significant bits holds version number 5
  334. (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000,
  335. // 16 bits, 8 bits for "clk_seq_hi_res",
  336. // 8 bits for "clk_seq_low",
  337. // two most significant bits holds zero and one for variant DCE1.1
  338. (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,
  339. // 48 bits for "node"
  340. substr($hash, 20, 12)
  341. ), self::V5, $namespace, $name);
  342. }
  343. /**
  344. * Version 4 UUIDs are random.
  345. *
  346. * @see https://en.wikipedia.org/wiki/UUID#Version_4_.28random.29
  347. *
  348. * @return self
  349. * A properly formatted v4 UUID.
  350. */
  351. public static function createV4() {
  352. return new self (sprintf('{%04x%04x-%04x-%04x-%04x-%04x%04x%04x}',
  353. // 32 bits for "time_low"
  354. mt_rand(0, 65535), mt_rand(0, 65535),
  355. // 16 bits for "time_mid"
  356. mt_rand(0, 65535),
  357. // 12 bits before the 0100 of (version) 4 for "time_hi_and_version"
  358. mt_rand(0, 4095) | 0x4000,
  359. // 16 bits, 8 bits for "clk_seq_hi_res",
  360. // 8 bits for "clk_seq_low",
  361. // two most significant bits holds zero and one for variant DCE1.1
  362. mt_rand(0, 0x3fff) | 0x8000,
  363. // 48 bits for "node"
  364. mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)
  365. ));
  366. }
  367. /**
  368. * Version 3 UUID are based on namespace and name utilizing a md5 hash.
  369. *
  370. * If you are considering using v3 consider using v5 instead as that is what
  371. * is recommended.
  372. *
  373. * @see https://en.wikipedia.org/wiki/UUID#Version_3_.28MD5_hash.29
  374. */
  375. public static function createV3($namespace, $name) {
  376. // If the namespace is not a valid UUID we throw an error.
  377. if (!self::isValid($namespace)) {
  378. throw new Exception('The UUID provided for the namespace is not valid.');
  379. }
  380. $bin = self::bin($namespace);
  381. $hash = md5($bin . $name);
  382. return new self (sprintf('{%08s-%04s-%04x-%04x-%12s}',
  383. // 32 bits for "time_low"
  384. substr($hash, 0, 8),
  385. // 16 bits for "time_mid"
  386. substr($hash, 8, 4),
  387. // 16 bits for "time_hi_and_version",
  388. // four most significant bits holds version number 3
  389. (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000,
  390. // 16 bits, 8 bits for "clk_seq_hi_res",
  391. // 8 bits for "clk_seq_low",
  392. // two most significant bits holds zero and one for variant DCE1.1
  393. (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,
  394. // 48 bits for "node"
  395. substr($hash, 20, 12)
  396. ), self::V3, $namespace, $name);
  397. }
  398. /**
  399. * Utility function to convert hex into bin for a UUID.
  400. *
  401. * @param string $uuid
  402. * A UUID to convert into binary format.
  403. *
  404. * @return string
  405. * A UUID in binary format.
  406. */
  407. public static function bin($uuid) {
  408. if (!self::isValid($uuid)) {
  409. throw new Exception('The UUID provided for the namespace is not valid.');
  410. }
  411. // Get hexadecimal components of namespace
  412. $hex = str_replace(['-', '{', '}'], '', $uuid);
  413. $bin = '';
  414. // Convert to bits
  415. for ($i = 0; $i < strlen($hex); $i += 2) {
  416. $bin .= chr(hexdec($hex[$i] . $hex[$i + 1]));
  417. }
  418. return $bin;
  419. }
  420. }