diff --git a/PHP/.gitignore b/PHP/.gitignore
new file mode 100644
index 0000000..0ea089d
--- /dev/null
+++ b/PHP/.gitignore
@@ -0,0 +1,37 @@
+*.lo
+*.la
+.libs
+.deps
+acinclude.m4
+aclocal.m4
+autom4te.cache
+build
+config.guess
+config.h
+config.h.in
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure.ac
+include
+install-sh
+libtool
+ltmain.sh
+# Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+missing
+mkinstalldirs
+modules
+run-tests.php
+tests/*/*.diff
+tests/*/*.out
+tests/*/*.php
+tests/*/*.exp
+tests/*/*.log
+tests/*/*.sh
+.vscode/
+vendor/
diff --git a/PHP/.travis.yml b/PHP/.travis.yml
new file mode 100644
index 0000000..41c4afa
--- /dev/null
+++ b/PHP/.travis.yml
@@ -0,0 +1,36 @@
+language: php
+
+compiler:
+ - gcc
+ - clang
+
+os:
+ - linux
+
+
+php:
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+ - 7.4
+ - 8.0
+# - nightly
+
+notifications:
+ email: 63851587@qq.com
+
+env:
+ - REPORT_EXIT_STATUS=1 NO_INTERACTION=1
+
+before_install:
+ - chmod +x ./travis/compile.sh
+ - chmod +x ./travis/run-tests.sh
+
+#Compile
+before_script:
+ - ./travis/compile.sh
+
+# Run PHPs run-tests.php
+script:
+ - ./travis/run-tests.sh
\ No newline at end of file
diff --git a/PHP/CREDITS b/PHP/CREDITS
new file mode 100644
index 0000000..6812075
--- /dev/null
+++ b/PHP/CREDITS
@@ -0,0 +1,2 @@
+snowdrift
+63851587@qq.com
\ No newline at end of file
diff --git a/PHP/EXPERIMENTAL b/PHP/EXPERIMENTAL
new file mode 100644
index 0000000..e69de29
diff --git a/PHP/LICENSE b/PHP/LICENSE
new file mode 100644
index 0000000..808c001
--- /dev/null
+++ b/PHP/LICENSE
@@ -0,0 +1,68 @@
+--------------------------------------------------------------------
+ The PHP License, version 3.01
+Copyright (c) 1999 - 2011 The PHP Group. All rights reserved.
+--------------------------------------------------------------------
+
+Redistribution and use in source and binary forms, with or without
+modification, is permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ 3. The name "PHP" must not be used to endorse or promote products
+ derived from this software without prior written permission. For
+ written permission, please contact group@php.net.
+
+ 4. Products derived from this software may not be called "PHP", nor
+ may "PHP" appear in their name, without prior written permission
+ from group@php.net. You may indicate that your software works in
+ conjunction with PHP by saying "Foo for PHP" instead of calling
+ it "PHP Foo" or "phpfoo"
+
+ 5. The PHP Group may publish revised and/or new versions of the
+ license from time to time. Each version will be given a
+ distinguishing version number.
+ Once covered code has been published under a particular version
+ of the license, you may always continue to use it under the terms
+ of that version. You may also choose to use such covered code
+ under the terms of any subsequent version of the license
+ published by the PHP Group. No one other than the PHP Group has
+ the right to modify the terms applicable to covered code created
+ under this License.
+
+ 6. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes PHP software, freely available from
+ ".
+
+THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
+ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
+DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------
+
+This software consists of voluntary contributions made by many
+individuals on behalf of the PHP Group.
+
+The PHP Group can be contacted via Email at group@php.net.
+
+For more information on the PHP Group and the PHP project,
+please see .
+
+PHP includes the Zend Engine, freely available at
+.
diff --git a/PHP/README.md b/PHP/README.md
new file mode 100644
index 0000000..3e21322
--- /dev/null
+++ b/PHP/README.md
@@ -0,0 +1,50 @@
+# ❄ idenerator-php-extension
+
+## 介绍
+项目更多介绍参照:https://github.com/yitter/idgenerator
+
+## PHP环境
+
+* PHP7 or later
+
+## 安装方式
+
+```shell
+git clone https://gitee.com/yitter/idgenerator.git
+cd idgenerator/PHP
+phpize
+./configure --with-php-config=/path/php-config
+make
+make install
+```
+
+## 如何使用(PHP)
+
+**配置文件配置参数**:
+```shell
+//snowdrift.ini
+snowdrift.Method=1 //1 漂移算法 2 传统算法
+snowdrift.BaseTime=1582136402000
+snowdrift.WorkerId=1 //默认workerid
+snowdrift.WorkerIdNum=1 //支持的WorkerId数量,默认1,不超过(-1L << snowdrift.WorkerIdBitLength) ^ -1L
+snowdrift.WorkerIdBitLength=6
+snowdrift.SeqBitLength=6 //自增序号位数
+snowdrift.MaxSeqNumber=0
+snowdrift.MinSeqNumber=0
+snowdrift.TopOverCostCount=2000 //最大漂移次数
+```
+
+**函数签名**:
+```php
+\SnowDrift::NextId(int $wid=snowdrift.WorkerId):?int //获取单个id,$wid可选,默认值=snowdrift.WorkerId
+\SnowDrift::NextNumId(int $num, int $wid=snowdrift.WorkerId):?array //获取$num个id,$wid可选,默认值=snowdrift.WorkerId
+```
+
+**调用示例**
+```php
+$id=\SnowDrift::NextId();
+$id=\SnowDrift::NextId(3);
+
+$ids=\SnowDrift::NextNumId(10000);
+$ids=\SnowDrift::NextNumId(10000,3);
+```
diff --git a/PHP/config.m4 b/PHP/config.m4
new file mode 100644
index 0000000..25d77ed
--- /dev/null
+++ b/PHP/config.m4
@@ -0,0 +1,85 @@
+dnl $Id$
+dnl config.m4 for extension snowdrift
+
+dnl Comments in this file start with the string 'dnl'.
+dnl Remove where necessary. This file will not work
+dnl without editing.
+
+dnl If your extension references something external, use with:
+
+dnl PHP_ARG_WITH(snowdrift, for snowdrift support,
+dnl Make sure that the comment is aligned:
+dnl [ --with-snowdrift Include snowdrift support])
+
+dnl Otherwise use enable:
+
+PHP_ARG_ENABLE(snowdrift, whether to enable snowdrift support,
+dnl Make sure that the comment is aligned:
+[ --enable-snowdrift Enable snowdrift support])
+
+if test "$PHP_SNOWDRIFT" != "no"; then
+ dnl Write more examples of tests here...
+
+ dnl # get library FOO build options from pkg-config output
+ dnl AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
+ dnl AC_MSG_CHECKING(for libfoo)
+ dnl if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists foo; then
+ dnl if $PKG_CONFIG foo --atleast-version 1.2.3; then
+ dnl LIBFOO_CFLAGS=`$PKG_CONFIG foo --cflags`
+ dnl LIBFOO_LIBDIR=`$PKG_CONFIG foo --libs`
+ dnl LIBFOO_VERSON=`$PKG_CONFIG foo --modversion`
+ dnl AC_MSG_RESULT(from pkgconfig: version $LIBFOO_VERSON)
+ dnl else
+ dnl AC_MSG_ERROR(system libfoo is too old: version 1.2.3 required)
+ dnl fi
+ dnl else
+ dnl AC_MSG_ERROR(pkg-config not found)
+ dnl fi
+ dnl PHP_EVAL_LIBLINE($LIBFOO_LIBDIR, SNOWDRIFT_SHARED_LIBADD)
+ dnl PHP_EVAL_INCLINE($LIBFOO_CFLAGS)
+
+ dnl # --with-snowdrift -> check with-path
+ dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
+ dnl SEARCH_FOR="/include/snowdrift.h" # you most likely want to change this
+ dnl if test -r $PHP_SNOWDRIFT/$SEARCH_FOR; then # path given as parameter
+ dnl SNOWDRIFT_DIR=$PHP_SNOWDRIFT
+ dnl else # search default path list
+ dnl AC_MSG_CHECKING([for snowdrift files in default path])
+ dnl for i in $SEARCH_PATH ; do
+ dnl if test -r $i/$SEARCH_FOR; then
+ dnl SNOWDRIFT_DIR=$i
+ dnl AC_MSG_RESULT(found in $i)
+ dnl fi
+ dnl done
+ dnl fi
+ dnl
+ dnl if test -z "$SNOWDRIFT_DIR"; then
+ dnl AC_MSG_RESULT([not found])
+ dnl AC_MSG_ERROR([Please reinstall the snowdrift distribution])
+ dnl fi
+
+ dnl # --with-snowdrift -> add include path
+ dnl PHP_ADD_INCLUDE($SNOWDRIFT_DIR/include)
+
+ dnl # --with-snowdrift -> check for lib and symbol presence
+ dnl LIBNAME=snowdrift # you may want to change this
+ dnl LIBSYMBOL=snowdrift # you most likely want to change this
+
+ dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
+ dnl [
+ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SNOWDRIFT_DIR/$PHP_LIBDIR, SNOWDRIFT_SHARED_LIBADD)
+ dnl AC_DEFINE(HAVE_SNOWDRIFTLIB,1,[ ])
+ dnl ],[
+ dnl AC_MSG_ERROR([wrong snowdrift lib version or lib not found])
+ dnl ],[
+ dnl -L$SNOWDRIFT_DIR/$PHP_LIBDIR -lm
+ dnl ])
+ dnl
+ dnl PHP_SUBST(SNOWDRIFT_SHARED_LIBADD)
+ snowdrift_source_file="snowdrift.c\
+ src/snowflake/snowflake.c\
+ src/snowflake/shm.c\
+ src/snowflake/spinlock.c
+ "
+ PHP_NEW_EXTENSION(snowdrift, $snowdrift_source_file, $ext_shared)
+fi
diff --git a/PHP/config.w32 b/PHP/config.w32
new file mode 100644
index 0000000..8e8a6f7
--- /dev/null
+++ b/PHP/config.w32
@@ -0,0 +1,17 @@
+// $Id$
+// vim:ft=javascript
+
+// If your extension references something external, use ARG_WITH
+// ARG_WITH("snowdrift", "for snowdrift support", "no");
+
+// Otherwise, use ARG_ENABLE
+ARG_ENABLE("snowdrift", "enable snowdrift support", "no");
+
+if (PHP_SNOWDRIFT != "no") {
+ THIS_DIR=`dirname $0`
+ snowdrift_source_file="snowdrift.c\
+ $THIS_DIR/src/snowflake/snowflake.c
+ "
+ EXTENSION("snowdrift", $snowdrift_source_file, PHP_EXTNAME_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
+}
+
diff --git a/PHP/php_snowdrift.h b/PHP/php_snowdrift.h
new file mode 100644
index 0000000..22b5d06
--- /dev/null
+++ b/PHP/php_snowdrift.h
@@ -0,0 +1,75 @@
+/*
+ +----------------------------------------------------------------------+
+ | snowdrift |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2018 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: 63851587@qq.com |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef PHP_SNOWDRIFT_H
+#define PHP_SNOWDRIFT_H
+
+extern zend_module_entry snowdrift_module_entry;
+#define phpext_snowdrift_ptr &snowdrift_module_entry
+
+#define PHP_SNOWDRIFT_VERSION \
+ "1.0.0" /* Replace with version number for your extension */
+
+#ifdef PHP_WIN32
+#define PHP_SNOWDRIFT_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+#define PHP_SNOWDRIFT_API __attribute__((visibility("default")))
+#else
+#define PHP_SNOWDRIFT_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+// PHP8
+#if PHP_VERSION_ID >= 80000
+#define TSRMLS_D void
+#define TSRMLS_DC
+#define TSRMLS_C
+#define TSRMLS_CC
+#define TSRMLS_FETCH()
+
+#define ZEND_ACC_DTOR 0
+#endif
+
+ZEND_BEGIN_MODULE_GLOBALS(snowdrift)
+uint8_t Method;
+uint64_t BaseTime;
+uint8_t WorkerId;
+uint8_t WorkerIdNum;
+uint8_t WorkerIdBitLength;
+uint8_t SeqBitLength;
+uint32_t MaxSeqNumber;
+uint32_t MinSeqNumber;
+uint16_t TopOverCostCount;
+
+ZEND_END_MODULE_GLOBALS(snowdrift)
+
+ZEND_DECLARE_MODULE_GLOBALS(snowdrift)
+
+#define SD_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(snowdrift, v)
+
+#if defined(ZTS) && defined(COMPILE_DL_SNOWDRIFT)
+ZEND_TSRMLS_CACHE_EXTERN()
+#endif
+static int snowdrift_init();
+
+#endif /* PHP_SNOWDRIFT_H */
diff --git a/PHP/snowdrift.c b/PHP/snowdrift.c
new file mode 100644
index 0000000..c92f5c9
--- /dev/null
+++ b/PHP/snowdrift.c
@@ -0,0 +1,247 @@
+/*
+ +----------------------------------------------------------------------+
+ | snowdrift |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 1997-2018 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: 63851587@qq.com |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "zend_exceptions.h"
+#include "ext/standard/info.h"
+#include "src/snowflake/shm.h"
+#include "php_snowdrift.h"
+#include "src/snowflake/snowflake.h"
+
+/* True global resources - no need for thread safety here */
+static struct shm shmctx;
+static snowflake *sf;
+zend_class_entry snowdrift_ce;
+
+uint8_t num = 0;
+
+/* {{{ PHP_INI */
+
+PHP_INI_BEGIN()
+STD_PHP_INI_ENTRY("snowdrift.Method", "1", PHP_INI_SYSTEM, OnUpdateLongGEZero, Method, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.BaseTime", "1582136402000", PHP_INI_SYSTEM, OnUpdateLongGEZero, BaseTime, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.WorkerId", "1", PHP_INI_SYSTEM, OnUpdateLongGEZero, WorkerId, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.WorkerIdNum", "1", PHP_INI_SYSTEM, OnUpdateLongGEZero, WorkerIdNum, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.WorkerIdBitLength", "6", PHP_INI_SYSTEM, OnUpdateLongGEZero, WorkerIdBitLength, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.SeqBitLength", "6", PHP_INI_SYSTEM, OnUpdateLongGEZero, SeqBitLength, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.MaxSeqNumber", "0", PHP_INI_SYSTEM, OnUpdateLongGEZero, MaxSeqNumber, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.MinSeqNumber", "0", PHP_INI_SYSTEM, OnUpdateLongGEZero, MinSeqNumber, zend_snowdrift_globals, snowdrift_globals)
+STD_PHP_INI_ENTRY("snowdrift.TopOverCostCount", "2000", PHP_INI_SYSTEM, OnUpdateLongGEZero, TopOverCostCount, zend_snowdrift_globals, snowdrift_globals)
+PHP_INI_END()
+
+/* }}} */
+
+static int snowdrift_init()
+{
+ num = (-1L << SD_G(WorkerIdBitLength)) ^ -1L;
+ if (SD_G(WorkerIdNum) < num)
+ {
+ num = SD_G(WorkerIdNum);
+ }
+ shmctx.size = num * sizeof(snowflake);
+ if (shm_alloc(&shmctx) == -1)
+ {
+ zend_throw_exception_ex(NULL, 0, "shared memory malloc failed");
+ RETURN_FALSE;
+ }
+ if (SD_G(MaxSeqNumber) <= SD_G(MinSeqNumber))
+ {
+ zend_throw_exception_ex(NULL, 0, "MaxSeqNumber must GE then MinSeqNumber");
+ RETURN_FALSE;
+ }
+ bzero(shmctx.addr, num * sizeof(snowflake));
+ sf = (snowflake *)shmctx.addr;
+ for (int i = 0; i < num; i++)
+ {
+ snowflake *tmp = (sf + i);
+ tmp->Method = SD_G(Method);
+ tmp->BaseTime = SD_G(BaseTime);
+ tmp->WorkerId = i + 1;
+ tmp->WorkerIdBitLength = SD_G(WorkerIdBitLength);
+ tmp->SeqBitLength = SD_G(SeqBitLength);
+ tmp->MaxSeqNumber = SD_G(MaxSeqNumber);
+ tmp->MinSeqNumber = SD_G(MinSeqNumber);
+ tmp->TopOverCostCount = SD_G(TopOverCostCount);
+ Config(tmp);
+ }
+ return SUCCESS;
+}
+
+PHP_METHOD(snowdrift, NextId)
+{
+ zend_long wid = SD_G(WorkerId);
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &wid) == FAILURE)
+ {
+ RETURN_FALSE;
+ }
+ wid--;
+ if (wid < 0 || wid > num - 1)
+ {
+ zend_throw_exception_ex(NULL, 0, "wid error! wid between 0 and %d", num - 1);
+ RETURN_NULL();
+ }
+ snowflake *flake = (sf + wid);
+ RETURN_LONG(NextId(flake));
+}
+
+/** 这种方式性能比不上PHP直接循环调用NextId快
+ */
+PHP_METHOD(snowdrift, NextNumId)
+{
+ zend_long num = 1;
+ zend_long wid = SD_G(WorkerId);
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &num, &wid) == FAILURE)
+ {
+ RETURN_FALSE;
+ }
+ wid--;
+ if (wid < 0 || wid > num - 1)
+ {
+ zend_throw_exception_ex(NULL, 0, "wid error! wid between 0 and %d", num - 1);
+ RETURN_NULL();
+ }
+ snowflake *flake = (sf + wid);
+ array_init(return_value);
+ for (int i = 0; i < num; i++)
+ {
+ add_next_index_long(return_value, NextId(flake));
+ }
+}
+
+/* {{{ PHP_MSHUTDOWN_FUNCTION
+ */
+PHP_MSHUTDOWN_FUNCTION(snowdrift)
+{
+ UNREGISTER_INI_ENTRIES();
+ shm_free(&shmctx);
+ return SUCCESS;
+}
+/* }}} */
+
+/* Remove if there's nothing to do at request start */
+/* {{{ PHP_RINIT_FUNCTION
+ */
+PHP_RINIT_FUNCTION(snowdrift)
+{
+#if defined(COMPILE_DL_SNOWFLAKE) && defined(ZTS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+ return SUCCESS;
+}
+/* }}} */
+
+/* Remove if there's nothing to do at request end */
+/* {{{ PHP_RSHUTDOWN_FUNCTION
+ */
+PHP_RSHUTDOWN_FUNCTION(snowdrift)
+{
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MINFO_FUNCTION
+ */
+PHP_MINFO_FUNCTION(snowdrift)
+{
+ php_info_print_table_start();
+ php_info_print_table_header(2, "snowfrift support", "enabled");
+ php_info_print_table_row(2, "Version", PHP_SNOWDRIFT_VERSION);
+ php_info_print_table_end();
+
+ /* Remove comments if you have entries in php.ini */
+ DISPLAY_INI_ENTRIES();
+}
+/* }}} */
+
+/* {{{ arginfo
+ */
+ZEND_BEGIN_ARG_INFO(arginfo_snowdrift_void, 0)
+ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO_EX(arginfo_snowdrift_nextid, 0, 0, 1)
+ZEND_ARG_INFO(0, wid)
+ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO_EX(arginfo_snowdrift_nextnumid, 0, 0, 1)
+ZEND_ARG_INFO(0, num)
+ZEND_ARG_INFO(0, wid)
+ZEND_END_ARG_INFO()
+/* }}} */
+
+/* {{{ snowdrift_functions[]
+ *
+ * Every user visible function must have an entry in snowdrift_functions[].
+ */
+const zend_function_entry snowdrift_functions[] = {
+ PHP_FE_END /* Must be the last line in snowdrift_functions[] */
+};
+/* }}} */
+
+/* {{{ snowdrift_method[]
+ *
+ * Every user visible function must have an entry in snowdrift_functions[].
+ */
+static const zend_function_entry snowdrift_methods[] = {
+ PHP_ME(snowdrift, NextId, arginfo_snowdrift_nextid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
+ PHP_ME(snowdrift, NextNumId, arginfo_snowdrift_nextnumid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END};
+/* }}} */
+
+/* {{{ PHP_MINIT_FUNCTION
+ */
+PHP_MINIT_FUNCTION(snowdrift)
+{
+ INIT_CLASS_ENTRY(snowdrift_ce, "snowdrift", snowdrift_methods);
+ REGISTER_INI_ENTRIES();
+ zend_register_internal_class(&snowdrift_ce);
+
+ if (snowdrift_init() == FAILURE)
+ {
+ return FAILURE;
+ }
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ snowdrift_module_entry
+ */
+zend_module_entry snowdrift_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "snowdrift",
+ snowdrift_functions,
+ PHP_MINIT(snowdrift),
+ PHP_MSHUTDOWN(snowdrift),
+ PHP_RINIT(snowdrift), /* Replace with NULL if there's nothing to do at
+ request start */
+ PHP_RSHUTDOWN(snowdrift), /* Replace with NULL if there's nothing to do at
+ request end */
+ PHP_MINFO(snowdrift),
+ PHP_SNOWDRIFT_VERSION,
+ STANDARD_MODULE_PROPERTIES};
+/* }}} */
+
+#ifdef COMPILE_DL_SNOWDRIFT
+#ifdef ZTS
+ZEND_TSRMLS_CACHE_DEFINE()
+#endif
+ZEND_GET_MODULE(snowdrift)
+#endif
diff --git a/PHP/snowdrift.php b/PHP/snowdrift.php
new file mode 100644
index 0000000..31830c7
--- /dev/null
+++ b/PHP/snowdrift.php
@@ -0,0 +1,94 @@
+ 1, 'BaseTime' => 0, 'WorkerId' => 1, 'WorkerIdBitLength' => 6, 'SeqBitLength' => 10, 'TopOverCostCount' => 2000])
+ {
+ $this->ffi = FFI::cdef(file_get_contents($this->getHeaders()), $this->getLibrary());
+ $this->flake = $this->ffi->new("struct snowflake");
+ foreach ($config as $name => $val) {
+ $this->flake->$name = $val;
+ }
+ $this->pflake = FFI::addr($this->flake);
+ $this->ffi->Config($this->pflake);
+ }
+
+ public function getFFI(): FFI
+ {
+ return $this->ffi;
+ }
+
+ public function getFlake(): CData
+ {
+ return $this->flake;
+ }
+
+ public function getPflake(): CData
+ {
+ return $this->pflake;
+ }
+
+ public function nextId(): int
+ {
+ return $this->ffi->NextId($this->pflake);
+ }
+
+ public function getHeaders(): string
+ {
+ return __DIR__ . '/src/snowflake/snowflake.h';
+ }
+
+ public function getLibrary(): ?string
+ {
+ return __DIR__ . '/src/snowflake/libsnow.so';
+ }
+}
+
+$total = 50000;
+
+$snowflake = new SnowFlake(['Method' => 1, 'BaseTime' => 1577808000000, 'WorkerId' => 1, 'WorkerIdBitLength' => 1, 'SeqBitLength' => 10, 'TopOverCostCount' => 2000]);
+$ffi = $snowflake->getFFI();
+$pflake = $snowflake->getPflake();
+
+// $res = [];
+$start = microtime(true);
+for ($i = 0; $i < $total; $i++) {
+ // $res[] = \SnowDrift::NextId();
+ \SnowDrift::NextId(2);
+}
+echo sprintf("扩展漂移算法,PHP循环获取:%d,%s ms", $total, ((microtime(true) - $start)) * 1000) . PHP_EOL;
+
+// $res = [];
+$start = microtime(true);
+foreach (\SnowDrift::NextNumId($total) as $val) {
+ // $res[] = $val;
+}
+echo sprintf("扩展漂移算法,C循环获取:%d,%s ms", $total, ((microtime(true) - $start)) * 1000) . PHP_EOL;
+
+// $res = [];
+$start = microtime(true);
+for ($i = 0; $i < $total; $i++) {
+ // $res[] = $ffi->NextId($pflake);
+ $ffi->NextId($pflake);
+}
+echo sprintf("FFI漂移算法,PHP循环获取:%d,%s ms", $total, ((microtime(true) - $start)) * 1000) . PHP_EOL;
+
+// $res = [];
+$start = microtime(true);
+$tmp = $ffi->NextNumId($pflake, $total);
+for ($i = 0; $i < $total; $i++) {
+ // $res[] = $tmp[$i];
+}
+echo sprintf("FFI漂移算法,C循环获取:%d,%s ms", $total, ((microtime(true) - $start)) * 1000) . PHP_EOL;
diff --git a/PHP/src/makefile b/PHP/src/makefile
new file mode 100644
index 0000000..662d491
--- /dev/null
+++ b/PHP/src/makefile
@@ -0,0 +1,34 @@
+#Makefile
+
+#自定义变量
+CC = gcc
+#编译选项,生成所有警告、不优化、采用c++11标准、输出调试信息、只编译并生成目标文件
+CFLAGS = -Wall -O2 -g -c
+FILE = ./test.c
+#wildcard为Makefile模式匹配关键字,获取目标目录符合匹配模式的所有文件名
+SRCS = $(FILE) $(wildcard ./snowflake/*.c)
+#patsubst为Makefile模式替换关键字,查找字符串SRCS中按空格分开的单词,并将符合模式%.cpp的字符串全部替换成%.o
+OBJS = $(patsubst ./%.c, ./%.o, $(SRCS))
+EXES = test
+RM = rm -f
+
+#默认任务
+default:
+#默认任务要执行的命令,按上面的变量名替换为变量值后执行,前面必须有一个Tab符
+ $(MAKE) $(EXES)
+
+#模式匹配,冒号前者为目标项,冒号后面为依赖项
+$(EXES): $(OBJS)
+#$^表示规则中所有的依赖项,$@表示规则中的目标
+ $(CC) $^ -lm -lpthread -o $@
+
+# %模式自动匹配符
+%.o: %.c
+# $<表示规则中的第一个依赖项
+ $(CC) $(CFLAGS) $< -o $@
+
+#伪目标,声明clean为伪目标或标签,为了避免该清理任务与文件名相同而被错识别
+.PHONY: clean
+clean:
+#清理之前的目标文件,以便下次完整的重新编译
+ $(RM) $(OBJS) $(EXES)
\ No newline at end of file
diff --git a/PHP/src/snowflake/shm.c b/PHP/src/snowflake/shm.c
new file mode 100644
index 0000000..942e5bb
--- /dev/null
+++ b/PHP/src/snowflake/shm.c
@@ -0,0 +1,65 @@
+#include
+#include
+#include
+#include
+#include "shm.h"
+
+#ifdef MAP_ANON
+
+int shm_alloc(struct shm *shm)
+{
+ shm->addr = (void *)mmap(NULL, shm->size,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+
+ if (shm->addr == NULL)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+void shm_free(struct shm *shm)
+{
+ if (shm->addr)
+ {
+ munmap((void *)shm->addr, shm->size);
+ }
+}
+
+#else
+
+int shm_alloc(struct shm *shm)
+{
+ int fd;
+
+ fd = open("/dev/zero", O_RDWR);
+ if (fd == -1)
+ {
+ return -1;
+ }
+
+ shm->addr = (void *)mmap(NULL, shm->size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+
+ close(fd);
+
+ if (shm->addr == NULL)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+void shm_free(struct shm *shm)
+{
+ if (shm->addr)
+ {
+ munmap((void *)shm->addr, shm->size);
+ }
+}
+
+#endif
diff --git a/PHP/src/snowflake/shm.h b/PHP/src/snowflake/shm.h
new file mode 100644
index 0000000..8a1304f
--- /dev/null
+++ b/PHP/src/snowflake/shm.h
@@ -0,0 +1,14 @@
+#ifndef __SHM_H
+#define __SHM_H
+
+struct shm
+{
+ void *addr;
+ size_t size;
+};
+
+int shm_alloc(struct shm *shm);
+
+void shm_free(struct shm *shm);
+
+#endif
diff --git a/PHP/src/snowflake/snowflake.c b/PHP/src/snowflake/snowflake.c
new file mode 100644
index 0000000..5a298fb
--- /dev/null
+++ b/PHP/src/snowflake/snowflake.c
@@ -0,0 +1,207 @@
+#include
+#include
+#include
+#include "snowflake.h"
+#include "spinlock.h"
+
+#if defined(WIN32)
+#include "windows.h"
+#endif
+
+static void EndOverCostAction(uint64_t useTimeTick, snowflake *flake);
+static uint64_t NextOverCostId(snowflake *flake);
+static uint64_t NextNormalId(snowflake *flake);
+static uint64_t GetCurrentTimeTick(snowflake *flake);
+static uint64_t GetNextTimeTick(snowflake *flake);
+static uint64_t CalcId(snowflake *flake);
+static uint64_t CalcTurnBackId(snowflake *flake);
+
+int ncpu;
+uint16_t spin = 2048;
+uint32_t pid = 0;
+
+void Config(snowflake *flake)
+{
+ if (pid == 0)
+ {
+ pid = (uint32_t)getpid();
+#if defined(WIN32)
+ SYSTEM_INFO sysInfo;
+ GetSystemInfo(&sysInfo);
+ ncpu = sysInfo.dwNumberOfProcessors;
+#else
+ ncpu = sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+ if (ncpu <= 0)
+ {
+ ncpu = 1;
+ }
+ }
+ flake->WorkerIdBitLength = flake->WorkerIdBitLength == 0 ? 6 : flake->WorkerIdBitLength;
+ flake->SeqBitLength = flake->SeqBitLength == 0 ? 6 : flake->SeqBitLength;
+ flake->MaxSeqNumber = flake->MaxSeqNumber > 0 ? flake->MaxSeqNumber : (-1L << flake->SeqBitLength) ^ -1L;
+ flake->BaseTime = flake->BaseTime != 0 ? flake->BaseTime : 1577808000000;
+ flake->_TimestampShift = (uint8_t)(flake->WorkerIdBitLength + flake->SeqBitLength);
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ return;
+}
+
+void inline EndOverCostAction(uint64_t useTimeTick, snowflake *flake)
+{
+ if (flake->_TermIndex > 10000)
+ {
+ flake->_TermIndex = 0;
+ }
+}
+
+uint64_t inline NextOverCostId(snowflake *flake)
+{
+ uint64_t currentTimeTick = GetCurrentTimeTick(flake);
+ if (currentTimeTick > flake->_LastTimeTick)
+ {
+ EndOverCostAction(currentTimeTick, flake);
+ flake->_LastTimeTick = currentTimeTick;
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ flake->_IsOverCost = 0;
+ flake->_OverCostCountInOneTerm = 0;
+ flake->_GenCountInOneTerm = 0;
+ return CalcId(flake);
+ }
+ if (flake->_OverCostCountInOneTerm > flake->TopOverCostCount)
+ {
+ EndOverCostAction(currentTimeTick, flake);
+ flake->_LastTimeTick = GetNextTimeTick(flake);
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ flake->_IsOverCost = 0;
+ flake->_OverCostCountInOneTerm = 0;
+ flake->_GenCountInOneTerm = 0;
+ return CalcId(flake);
+ }
+ if (flake->_CurrentSeqNumber > flake->MaxSeqNumber)
+ {
+ flake->_LastTimeTick++;
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ flake->_IsOverCost = 1;
+ flake->_OverCostCountInOneTerm++;
+ flake->_GenCountInOneTerm++;
+ return CalcId(flake);
+ }
+
+ flake->_GenCountInOneTerm++;
+ return CalcId(flake);
+}
+
+uint64_t inline NextNormalId(snowflake *flake)
+{
+ uint64_t currentTimeTick = GetCurrentTimeTick(flake);
+ if (currentTimeTick < flake->_LastTimeTick)
+ {
+ if (flake->_TurnBackTimeTick < 1)
+ {
+ flake->_TurnBackTimeTick = flake->_LastTimeTick - 1;
+ flake->_TurnBackIndex++;
+ if (flake->_TurnBackIndex > 4)
+ {
+ flake->_TurnBackIndex = 1;
+ }
+ }
+ return CalcTurnBackId(flake);
+ }
+ if (flake->_TurnBackTimeTick > 0)
+ {
+ flake->_TurnBackTimeTick = 0;
+ }
+ if (currentTimeTick > flake->_LastTimeTick)
+ {
+ flake->_LastTimeTick = currentTimeTick;
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ return CalcId(flake);
+ }
+ if (flake->_CurrentSeqNumber > flake->MaxSeqNumber)
+ {
+ flake->_TermIndex++;
+ flake->_LastTimeTick++;
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ flake->_IsOverCost = 1;
+ flake->_OverCostCountInOneTerm = 1;
+ flake->_GenCountInOneTerm = 1;
+ return CalcId(flake);
+ }
+ return CalcId(flake);
+}
+
+uint64_t inline GetCurrentTimeTick(snowflake *flake)
+{
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ return (uint64_t)((t.tv_sec * 1000 + t.tv_usec / 1000) - flake->BaseTime);
+}
+
+uint64_t inline GetNextTimeTick(snowflake *flake)
+{
+ uint64_t tempTimeTicker = GetCurrentTimeTick(flake);
+ while (tempTimeTicker <= flake->_LastTimeTick)
+ {
+ tempTimeTicker = GetCurrentTimeTick(flake);
+ }
+ return tempTimeTicker;
+}
+
+uint64_t inline CalcId(snowflake *flake)
+{
+ uint64_t result = (flake->_LastTimeTick << flake->_TimestampShift) + (flake->WorkerId << flake->SeqBitLength) + (flake->_CurrentSeqNumber);
+ flake->_CurrentSeqNumber++;
+ return result;
+}
+
+uint64_t inline CalcTurnBackId(snowflake *flake)
+{
+ uint64_t result = (flake->_LastTimeTick << flake->_TimestampShift) + (flake->WorkerId << flake->SeqBitLength) + (flake->_TurnBackTimeTick);
+ flake->_TurnBackTimeTick--;
+ return result;
+}
+
+uint64_t inline NextSonwId(snowflake *flake)
+{
+ uint64_t currentTimeTick = GetCurrentTimeTick(flake);
+ if (flake->_LastTimeTick == currentTimeTick)
+ {
+ flake->_CurrentSeqNumber++;
+ if (flake->_CurrentSeqNumber > flake->MaxSeqNumber)
+ {
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ currentTimeTick = GetNextTimeTick(flake);
+ }
+ }
+ else
+ {
+ flake->_CurrentSeqNumber = flake->MinSeqNumber;
+ }
+ flake->_LastTimeTick = currentTimeTick;
+ return (uint64_t)((currentTimeTick << flake->_TimestampShift) | (flake->WorkerId << flake->SeqBitLength) | flake->_CurrentSeqNumber);
+}
+
+uint64_t inline GetId(snowflake *flake)
+{
+ return flake->Method == 1 ? (flake->_IsOverCost != 0 ? NextOverCostId(flake) : NextNormalId(flake)) : NextSonwId(flake);
+}
+
+uint64_t NextId(snowflake *flake)
+{
+ spin_lock(&flake->_Lock, pid);
+ uint64_t id = GetId(flake);
+ spin_unlock(&flake->_Lock, pid);
+ return id;
+}
+
+uint64_t *NextNumId(snowflake *flake, uint32_t num)
+{
+ uint64_t *arr = (uint64_t *)malloc(sizeof(uint64_t) * num);
+ spin_lock(&flake->_Lock, pid);
+ for (uint32_t i = 0; i < num; i++)
+ {
+ arr[i] = GetId(flake);
+ }
+ spin_unlock(&flake->_Lock, pid);
+ return arr;
+}
diff --git a/PHP/src/snowflake/snowflake.h b/PHP/src/snowflake/snowflake.h
new file mode 100644
index 0000000..7e2ac13
--- /dev/null
+++ b/PHP/src/snowflake/snowflake.h
@@ -0,0 +1,28 @@
+#include
+
+typedef struct snowflake
+{
+ uint8_t Method;
+ uint64_t BaseTime;
+ uint16_t WorkerId;
+ uint8_t WorkerIdBitLength;
+ uint8_t SeqBitLength;
+ uint32_t MaxSeqNumber;
+ uint32_t MinSeqNumber;
+ uint32_t TopOverCostCount;
+
+ uint8_t _TimestampShift;
+ uint32_t _CurrentSeqNumber;
+ int64_t _LastTimeTick;
+ int64_t _TurnBackTimeTick;
+ uint8_t _TurnBackIndex;
+ uint8_t _IsOverCost;
+ uint32_t _OverCostCountInOneTerm;
+ uint32_t _GenCountInOneTerm;
+ uint32_t _TermIndex;
+ volatile unsigned int _Lock;
+} snowflake;
+
+void Config(snowflake *flake);
+uint64_t NextId(snowflake *flake);
+uint64_t *NextNumId(snowflake *flake, uint32_t num);
\ No newline at end of file
diff --git a/PHP/src/snowflake/spinlock.c b/PHP/src/snowflake/spinlock.c
new file mode 100644
index 0000000..7a2de49
--- /dev/null
+++ b/PHP/src/snowflake/spinlock.c
@@ -0,0 +1,47 @@
+#include
+#include
+#include "spinlock.h"
+
+extern int ncpu;
+extern int spin;
+
+void spin_lock(atomic_t *lock, uint32_t pid)
+{
+ int i, n;
+
+ for (;;)
+ {
+
+ if (*lock == 0 &&
+ __sync_bool_compare_and_swap(lock, 0, pid))
+ {
+ return;
+ }
+
+ if (ncpu > 1)
+ {
+
+ for (n = 1; n < spin; n <<= 1)
+ {
+
+ for (i = 0; i < n; i++)
+ {
+ __asm("pause");
+ }
+
+ if (*lock == 0 &&
+ __sync_bool_compare_and_swap(lock, 0, pid))
+ {
+ return;
+ }
+ }
+ }
+
+ sched_yield();
+ }
+}
+
+void spin_unlock(atomic_t *lock, uint32_t pid)
+{
+ __sync_bool_compare_and_swap(lock, pid, 0);
+}
diff --git a/PHP/src/snowflake/spinlock.h b/PHP/src/snowflake/spinlock.h
new file mode 100644
index 0000000..c1535f1
--- /dev/null
+++ b/PHP/src/snowflake/spinlock.h
@@ -0,0 +1,11 @@
+#ifndef __SPINLOCK_H
+#define __SPINLOCK_H
+#include
+
+typedef volatile unsigned int atomic_t;
+
+extern void spin_lock(atomic_t *lock, uint32_t pid);
+
+extern void spin_unlock(atomic_t *lock, uint32_t pid);
+
+#endif
diff --git a/PHP/src/test.c b/PHP/src/test.c
new file mode 100644
index 0000000..533bf94
--- /dev/null
+++ b/PHP/src/test.c
@@ -0,0 +1,104 @@
+#include
+#include
+#include
+#include
+#include
+#include "snowflake/snowflake.h"
+
+#if defined(WIN32)
+#include "windows.h"
+#endif
+
+#define THREAD 2
+#define TOTAL 50000
+
+static snowflake snowf = {1, 0, 1, 6, 6, 0, 0, 2000};
+static snowflake *flake = &snowf;
+
+uint64_t arr[TOTAL];
+
+static uint64_t index = 0;
+
+uint64_t compar(const void *a, const void *b)
+{
+ return (*(uint64_t *)a - *(uint64_t *)b);
+}
+
+uint64_t containsDuplicate()
+{
+ uint32_t i, j;
+ qsort(arr, TOTAL, sizeof(uint64_t), compar);
+ for (i = 0, j = 1; j < TOTAL; i++, j++)
+ {
+ if (arr[i] > 0 && arr[j] > 0 && arr[i] == arr[j])
+ {
+ return j;
+ }
+ }
+ return 0;
+}
+
+void run()
+{
+ for (int i = 0; i < TOTAL / THREAD; i++)
+ {
+ arr[__sync_fetch_and_add(&index, 1)] = NextId(flake);
+ }
+}
+
+int main()
+{
+ flake->Method = 1;
+ Config(flake);
+ pthread_t tid[THREAD];
+ struct timeval t_start, t_end;
+ while (1)
+ {
+ // clock_gettime(CLOCK_REALTIME, &t_start);
+
+ // for (int i = 0; i < THREAD; i++)
+ // {
+ // if (pthread_create(&tid[i], NULL, (void *)run, NULL) != 0)
+ // {
+ // printf("thread creation failed\n");
+ // exit(1);
+ // }
+ // }
+
+ // for (int i = 0; i < THREAD; i++)
+ // {
+ // pthread_join(tid[i], NULL); //等待线程结束
+ // }
+
+ // clock_gettime(CLOCK_REALTIME, &t_end);
+
+ // printf("%d 线程 %s,总共:%d,%lf ms\n", THREAD, flake->Method == 1 ? "漂移" : "传统", index, (double)(t_end.tv_nsec - t_start.tv_nsec) / 1000000.0);
+
+ // uint64_t re = containsDuplicate();
+ // if (re > 0)
+ // {
+ // printf("有重复数据:%ld,%ld\n", arr[re - 1], arr[re]);
+ // }
+ // else
+ // {
+ // printf("没有重复数据\n");
+ // }
+
+ // for (int i = 0; i < TOTAL; i++)
+ // {
+ // arr[i] = 0;
+ // }
+ // index = 0;
+
+ gettimeofday(&t_start, NULL);
+ for (int i = 0; i < TOTAL; i++)
+ {
+ NextId(flake);
+ }
+ gettimeofday(&t_end, NULL);
+ printf("单线程 %s,总共:%d,%lf ms\n", flake->Method == 1 ? "漂移" : "传统", TOTAL, (double)(t_end.tv_usec - t_start.tv_usec) / 1000.0);
+ sleep(1);
+ }
+
+ return 1;
+}
\ No newline at end of file
diff --git a/PHP/tests/001.phpt b/PHP/tests/001.phpt
new file mode 100644
index 0000000..de59232
--- /dev/null
+++ b/PHP/tests/001.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Check for snowdrift presence
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+snowdrift extension is available
diff --git a/PHP/tests/002.phpt b/PHP/tests/002.phpt
new file mode 100644
index 0000000..fcc59eb
--- /dev/null
+++ b/PHP/tests/002.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Check for snowdrift unique
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+int(100000)
\ No newline at end of file
diff --git a/PHP/tests/003.phpt b/PHP/tests/003.phpt
new file mode 100644
index 0000000..b3d4647
--- /dev/null
+++ b/PHP/tests/003.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Check for snowdrift serial
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+bool(true)
\ No newline at end of file
diff --git a/PHP/tests/004.phpt b/PHP/tests/004.phpt
new file mode 100644
index 0000000..5a0564b
--- /dev/null
+++ b/PHP/tests/004.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Check for snowdrift batch get unique
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+int(100000)
\ No newline at end of file
diff --git a/PHP/travis/compile.sh b/PHP/travis/compile.sh
new file mode 100644
index 0000000..4de86a7
--- /dev/null
+++ b/PHP/travis/compile.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+phpize && ./configure && make clean && make && gcc -O2 -fPIC -shared -g src/snowflake/snowflake.c -o src/snowflake/libsnow.so
\ No newline at end of file
diff --git a/PHP/travis/run-tests.sh b/PHP/travis/run-tests.sh
new file mode 100644
index 0000000..1df9a84
--- /dev/null
+++ b/PHP/travis/run-tests.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+TEST_DIR="`pwd`/tests/"
+
+make test
+
+for file in `find $TEST_DIR -name "*.diff" 2>/dev/null`
+do
+ grep "\-\-XFAIL--" ${file/%diff/phpt} >/dev/null 2>&1
+ if [ $? -gt 0 ]
+ then
+ FAILS[${#FAILS[@]}]="$file"
+ fi
+done
+
+if [ ${#FAILS[@]} -gt 0 ]
+then
+ for fail in "${FAILS[@]}"
+ do
+ sh -xc "cat $fail"
+ done
+ exit 1
+else
+ exit 0
+fi
\ No newline at end of file