[THRIFT-5757] Unit tests for php lib
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b9afd50..f003db2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -119,10 +119,12 @@
           mkdir -p ./lib/php/test/Resources/packages/phpv
           mkdir -p ./lib/php/test/Resources/packages/phpvo
           mkdir -p ./lib/php/test/Resources/packages/phpjs
-          compiler/cpp/thrift --gen php              -r --out ./lib/php/test/Resources/packages/php    lib/php/test/Resources/ThriftTest.thrift
-          compiler/cpp/thrift --gen php:validate     -r --out ./lib/php/test/Resources/packages/phpv   lib/php/test/Resources/ThriftTest.thrift
-          compiler/cpp/thrift --gen php:validate,oop -r --out ./lib/php/test/Resources/packages/phpvo  lib/php/test/Resources/ThriftTest.thrift
-          compiler/cpp/thrift --gen php:json         -r --out ./lib/php/test/Resources/packages/phpjs  lib/php/test/Resources/ThriftTest.thrift
+          mkdir -p ./lib/php/test/Resources/packages/phpcm
+          compiler/cpp/thrift --gen php -r --out ./lib/php/test/Resources/packages/php lib/php/test/Resources/ThriftTest.thrift
+          compiler/cpp/thrift --gen php:validate -r --out ./lib/php/test/Resources/packages/phpv lib/php/test/Resources/ThriftTest.thrift
+          compiler/cpp/thrift --gen php:validate,oop -r --out ./lib/php/test/Resources/packages/phpvo lib/php/test/Resources/ThriftTest.thrift
+          compiler/cpp/thrift --gen php:json -r --out ./lib/php/test/Resources/packages/phpjs lib/php/test/Resources/ThriftTest.thrift
+          compiler/cpp/thrift --gen php:classmap,server,rest -r --out ./lib/php/test/Resources/packages/phpcm lib/php/test/Resources/ThriftTest.thrift
 
       - name: Run Tests
         run: vendor/bin/phpunit -c lib/php/phpunit.xml
diff --git a/lib/php/lib/ClassLoader/ThriftClassLoader.php b/lib/php/lib/ClassLoader/ThriftClassLoader.php
index e4b4a17..c1da4cb 100644
--- a/lib/php/lib/ClassLoader/ThriftClassLoader.php
+++ b/lib/php/lib/ClassLoader/ThriftClassLoader.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -35,7 +36,7 @@
 
     /**
      * Thrift definition paths
-     * @var type
+     * @var array
      */
     protected $definitions = array();
 
@@ -101,8 +102,9 @@
      */
     public function loadClass($class)
     {
-        if ((true === $this->apcu && ($file = $this->findFileInApcu($class))) or
-            ($file = $this->findFile($class))
+        if (
+            (true === $this->apcu && ($file = $this->findFileInApcu($class)))
+            || ($file = $this->findFile($class))
         ) {
             require_once $file;
         }
@@ -166,6 +168,7 @@
 
             // Ignore wrong call
             if (count($m) <= 1) {
+                #HOW TO TEST THIS? HOW TEST CASE SHOULD LOOK LIKE?
                 return;
             }
 
@@ -183,8 +186,9 @@
                      * Available in service: Interface, Client, Processor, Rest
                      * And every service methods (_.+)
                      */
-                    if (0 === preg_match('#(.+)(if|client|processor|rest)$#i', $class, $n) and
-                        0 === preg_match('#(.+)_[a-z0-9]+_(args|result)$#i', $class, $n)
+                    if (
+                        0 === preg_match('#(.+)(if|client|processor|rest)$#i', $class, $n)
+                        && 0 === preg_match('#(.+)_[a-z0-9]+_(args|result)$#i', $class, $n)
                     ) {
                         $className = 'Types';
                     } else {
diff --git a/lib/php/lib/Factory/TBinaryProtocolFactory.php b/lib/php/lib/Factory/TBinaryProtocolFactory.php
index 2519183..fc02d71 100644
--- a/lib/php/lib/Factory/TBinaryProtocolFactory.php
+++ b/lib/php/lib/Factory/TBinaryProtocolFactory.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -23,21 +24,36 @@
 namespace Thrift\Factory;
 
 use Thrift\Protocol\TBinaryProtocol;
+use Thrift\Transport\TTransport;
 
 /**
  * Binary Protocol Factory
  */
 class TBinaryProtocolFactory implements TProtocolFactory
 {
+    /**
+     * @var bool
+     */
     private $strictRead_ = false;
+    /**
+     * @var bool
+     */
     private $strictWrite_ = false;
 
+    /**
+     * @param bool $strictRead
+     * @param bool $strictWrite
+     */
     public function __construct($strictRead = false, $strictWrite = false)
     {
         $this->strictRead_ = $strictRead;
         $this->strictWrite_ = $strictWrite;
     }
 
+    /**
+     * @param TTransport $trans
+     * @return TBinaryProtocol
+     */
     public function getProtocol($trans)
     {
         return new TBinaryProtocol($trans, $this->strictRead_, $this->strictWrite_);
diff --git a/lib/php/lib/Factory/TCompactProtocolFactory.php b/lib/php/lib/Factory/TCompactProtocolFactory.php
index 11fb8ff..9171f7b 100644
--- a/lib/php/lib/Factory/TCompactProtocolFactory.php
+++ b/lib/php/lib/Factory/TCompactProtocolFactory.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -23,16 +24,17 @@
 namespace Thrift\Factory;
 
 use Thrift\Protocol\TCompactProtocol;
+use Thrift\Transport\TTransport;
 
 /**
  * Compact Protocol Factory
  */
 class TCompactProtocolFactory implements TProtocolFactory
 {
-    public function __construct()
-    {
-    }
-
+    /**
+     * @param TTransport $trans
+     * @return TCompactProtocol
+     */
     public function getProtocol($trans)
     {
         return new TCompactProtocol($trans);
diff --git a/lib/php/lib/Factory/TJSONProtocolFactory.php b/lib/php/lib/Factory/TJSONProtocolFactory.php
index fbfb1d7..44852d0 100644
--- a/lib/php/lib/Factory/TJSONProtocolFactory.php
+++ b/lib/php/lib/Factory/TJSONProtocolFactory.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -23,16 +24,17 @@
 namespace Thrift\Factory;
 
 use Thrift\Protocol\TJSONProtocol;
+use Thrift\Transport\TTransport;
 
 /**
  * JSON Protocol Factory
  */
 class TJSONProtocolFactory implements TProtocolFactory
 {
-    public function __construct()
-    {
-    }
-
+    /**
+     * @param TTransport $trans
+     * @return TJSONProtocol
+     */
     public function getProtocol($trans)
     {
         return new TJSONProtocol($trans);
diff --git a/lib/php/lib/Factory/TProtocolFactory.php b/lib/php/lib/Factory/TProtocolFactory.php
index d3066c8..be990e7 100644
--- a/lib/php/lib/Factory/TProtocolFactory.php
+++ b/lib/php/lib/Factory/TProtocolFactory.php
@@ -22,6 +22,8 @@
 
 namespace Thrift\Factory;
 
+use Thrift\Protocol\TProtocol;
+
 /**
  * Protocol factory creates protocol objects from transports
  */
@@ -30,7 +32,7 @@
     /**
      * Build a protocol from the base transport
      *
-     * @return Thrift\Protocol\TProtocol protocol
+     * @return TProtocol protocol
      */
     public function getProtocol($trans);
 }
diff --git a/lib/php/lib/Factory/TStringFuncFactory.php b/lib/php/lib/Factory/TStringFuncFactory.php
index 30de4d7..4b1f9d3 100644
--- a/lib/php/lib/Factory/TStringFuncFactory.php
+++ b/lib/php/lib/Factory/TStringFuncFactory.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
diff --git a/lib/php/lib/StringFunc/Core.php b/lib/php/lib/StringFunc/Core.php
index 376e437..45fb0fe 100644
--- a/lib/php/lib/StringFunc/Core.php
+++ b/lib/php/lib/StringFunc/Core.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -23,6 +24,12 @@
 
 class Core implements TStringFunc
 {
+    /**
+     * @param string $str
+     * @param int $start
+     * @param int|null $length
+     * @return false|string
+     */
     public function substr($str, $start, $length = null)
     {
         // specifying a null $length would return an empty string
@@ -33,6 +40,10 @@
         return substr((string) $str, $start, $length);
     }
 
+    /**
+     * @param string $str
+     * @return int
+     */
     public function strlen($str)
     {
         return strlen((string) $str);
diff --git a/lib/php/lib/StringFunc/Mbstring.php b/lib/php/lib/StringFunc/Mbstring.php
index ac48309..be7f38a 100644
--- a/lib/php/lib/StringFunc/Mbstring.php
+++ b/lib/php/lib/StringFunc/Mbstring.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -23,6 +24,12 @@
 
 class Mbstring implements TStringFunc
 {
+    /**
+     * @param string $str
+     * @param int $start
+     * @param int|null $length
+     * @return false|string
+     */
     public function substr($str, $start, $length = null)
     {
         /**
@@ -39,6 +46,10 @@
         return mb_substr((string) $str, $start, $length, '8bit');
     }
 
+    /**
+     * @param string $str
+     * @return int
+     */
     public function strlen($str)
     {
         return mb_strlen((string) $str, '8bit');
diff --git a/lib/php/lib/StringFunc/TStringFunc.php b/lib/php/lib/StringFunc/TStringFunc.php
index dea497f..2b7a2cf 100644
--- a/lib/php/lib/StringFunc/TStringFunc.php
+++ b/lib/php/lib/StringFunc/TStringFunc.php
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -23,6 +24,17 @@
 
 interface TStringFunc
 {
+    /**
+     * @param string $str
+     * @param int $start
+     * @param int|null $length
+     * @return false|string
+     */
     public function substr($str, $start, $length = null);
+
+    /**
+     * @param string $str
+     * @return int
+     */
     public function strlen($str);
 }
diff --git a/lib/php/test/Makefile.am b/lib/php/test/Makefile.am
index c2de405..00d51f6 100644
--- a/lib/php/test/Makefile.am
+++ b/lib/php/test/Makefile.am
@@ -24,10 +24,12 @@
 	mkdir -p ./Resources/packages/phpv
 	mkdir -p ./Resources/packages/phpvo
 	mkdir -p ./Resources/packages/phpjs
-	$(THRIFT) --gen php              -r --out ./Resources/packages/php    Resources/ThriftTest.thrift
-	$(THRIFT) --gen php:validate     -r --out ./Resources/packages/phpv   Resources/ThriftTest.thrift
-	$(THRIFT) --gen php:validate,oop -r --out ./Resources/packages/phpvo  Resources/ThriftTest.thrift
-	$(THRIFT) --gen php:json         -r --out ./Resources/packages/phpjs  Resources/ThriftTest.thrift
+	mkdir -p ./Resources/packages/phpcm
+	$(THRIFT) --gen php -r --out ./Resources/packages/php Resources/ThriftTest.thrift
+	$(THRIFT) --gen php:validate -r --out ./Resources/packages/phpv Resources/ThriftTest.thrift
+	$(THRIFT) --gen php:validate,oop -r --out ./Resources/packages/phpvo Resources/ThriftTest.thrift
+	$(THRIFT) --gen php:json -r --out ./Resources/packages/phpjs Resources/ThriftTest.thrift
+	$(THRIFT) --gen php:classmap,server,rest -r --out ./Resources/packages/phpcm Resources/ThriftTest.thrift
 
 deps: $(top_srcdir)/composer.json
 	composer install --working-dir=$(top_srcdir)
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php
new file mode 100644
index 0000000..e39f501
--- /dev/null
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * ClassLoader to load Thrift library and definitions
+ * Inspired from UniversalClassLoader from Symfony 2
+ *
+ * @package thrift.classloader
+ */
+
+namespace A;
+
+class TestClass
+{
+    public function __invoke()
+    {
+        return __CLASS__;
+    }
+}
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php
new file mode 100644
index 0000000..d2e87e2
--- /dev/null
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * ClassLoader to load Thrift library and definitions
+ * Inspired from UniversalClassLoader from Symfony 2
+ *
+ * @package thrift.classloader
+ */
+
+namespace B;
+
+class TestClass
+{
+    public function __invoke()
+    {
+        return __CLASS__;
+    }
+}
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php
new file mode 100644
index 0000000..9e4000c
--- /dev/null
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * ClassLoader to load Thrift library and definitions
+ * Inspired from UniversalClassLoader from Symfony 2
+ *
+ * @package thrift.classloader
+ */
+
+namespace C;
+
+class TestClass
+{
+    public function __invoke()
+    {
+        return __CLASS__;
+    }
+}
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php
new file mode 100644
index 0000000..c0cda0c
--- /dev/null
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * ClassLoader to load Thrift library and definitions
+ * Inspired from UniversalClassLoader from Symfony 2
+ *
+ * @package thrift.classloader
+ */
+
+namespace D;
+
+class TestClass
+{
+    public function __invoke()
+    {
+        return __CLASS__;
+    }
+}
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php
new file mode 100644
index 0000000..b1981b4
--- /dev/null
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * ClassLoader to load Thrift library and definitions
+ * Inspired from UniversalClassLoader from Symfony 2
+ *
+ * @package thrift.classloader
+ */
+
+namespace E;
+
+class TestClass
+{
+    public function __invoke()
+    {
+        return __CLASS__;
+    }
+}
diff --git a/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php b/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php
new file mode 100644
index 0000000..2fa05c2
--- /dev/null
+++ b/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php
@@ -0,0 +1,223 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+namespace Test\Thrift\Unit\Lib\ClassLoader;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\ClassLoader\ThriftClassLoader;
+
+/***
+ * This test depends on running the compiler against the ./Resources/ThriftTest.thrift file:
+ * lib/php/test$ ../../../compiler/cpp/thrift --gen php:classmap,server,rest -r  --out ./Resources/packages/phpcm ./Resources/ThriftTest.thrift
+ */
+class ThriftClassLoaderTest extends TestCase
+{
+    const APCU_PREFIX = 'test';
+
+    /**
+     * @dataProvider registerNamespaceDataProvider
+     */
+    public function testRegisterNamespace(
+        $namespaces,
+        $class,
+        $isClassExist = true,
+        $useApcu = false,
+        $apcuPrefix = null
+    ) {
+        $loader = new ThriftClassLoader($useApcu, $apcuPrefix);
+        foreach ($namespaces as $namespace => $paths) {
+            $loader->registerNamespace($namespace, $paths);
+        }
+        $loader->register();
+        $loader->loadClass($class);
+        if ($isClassExist) {
+            $this->assertTrue(class_exists($class, false), "->loadClass() loads '$class'");
+        } else {
+            $this->assertFalse(class_exists($class, false), "->loadClass() loads '$class'");
+        }
+    }
+
+    public function registerNamespaceDataProvider()
+    {
+        yield 'default' => [
+            'namespaces' => [
+                'A' => __DIR__ . '/Fixtures',
+            ],
+            'class' => 'A\TestClass',
+        ];
+        yield 'missedClass' => [
+            'namespaces' => [
+                'A' => __DIR__ . '/Fixtures',
+            ],
+            'class' => 'A\MissedClass',
+            'isClassExist' => false,
+        ];
+        yield 'pathAsArray' => [
+            'namespaces' => [
+                'B' => [__DIR__ . '/Fixtures'],
+            ],
+            'class' => 'B\TestClass',
+        ];
+        yield 'loadClassWithSlash' => [
+            'namespaces' => [
+                'C' => __DIR__ . '/Fixtures',
+            ],
+            'class' => '\C\TestClass',
+            ];
+        yield 'severalNamespaces' => [
+            'namespaces' => [
+                'D' => __DIR__ . '/Fixtures',
+                'E' => __DIR__ . '/Fixtures',
+            ],
+            'class' => '\E\TestClass',
+        ];
+        yield 'useApcu' => [
+            'namespaces' => [
+                'D' => __DIR__ . '/Fixtures',
+                'E' => __DIR__ . '/Fixtures',
+            ],
+            'class' => '\E\TestClass',
+            'isClassExist' => true,
+            'useApcu' => true,
+            'apcuPrefix' => self::APCU_PREFIX,
+        ];
+    }
+
+    /**
+     * @dataProvider registerDefinitionDataProvider
+     */
+    public function testRegisterDefinition(
+        $definitions,
+        $class,
+        $checkInterfaceExist = false,
+        $useApcu = false,
+        $apcuPrefix = null
+    ) {
+        $loader = new ThriftClassLoader($useApcu, $apcuPrefix);
+        foreach ($definitions as $namespace => $paths) {
+            $loader->registerDefinition($namespace, $paths);
+        }
+        $loader->register();
+
+        $loader->loadClass($class);
+        if ($checkInterfaceExist) {
+            $this->assertTrue(interface_exists($class, false), "->loadClass() loads '$class'");
+        } else {
+            $this->assertTrue(class_exists($class, false), "->loadClass() loads '$class'");
+        }
+    }
+
+    public function registerDefinitionDataProvider()
+    {
+        yield 'loadType' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => 'ThriftTest\Xtruct',
+        ];
+        yield 'loadInterface' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => '\ThriftTest\ThriftTestIf',
+            'checkInterfaceExist' => true,
+        ];
+        yield 'loadClient' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => '\ThriftTest\ThriftTestClient',
+        ];
+        yield 'loadProcessor' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => '\ThriftTest\ThriftTestProcessor',
+        ];
+        yield 'loadRest' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => '\ThriftTest\ThriftTestRest',
+        ];
+        yield 'load_args' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => '\ThriftTest\ThriftTest_testVoid_args',
+        ];
+        yield 'load_result' => [
+            'definitions' => [
+                'ThriftTest' => __DIR__ . '/../../../Resources/packages/phpcm',
+            ],
+            'class' => '\ThriftTest\ThriftTest_testVoid_result',
+        ];
+        yield 'pathAsArray' => [
+            'definitions' => [
+                'ThriftTest' => [__DIR__ . '/../../../Resources/packages/phpcm'],
+            ],
+            'class' => 'ThriftTest\Xtruct',
+        ];
+        yield 'severalDefinitions' => [
+            'definitions' => [
+                'ThriftTest' => [__DIR__ . '/../../../Resources/packages/phpcm'],
+                'TestValidators' => [__DIR__ . '/../../../Resources/packages/phpcm'],
+            ],
+            'class' => '\TestValidators\TestServiceClient',
+        ];
+        yield 'useApcu' => [
+            'definitions' => [
+                'ThriftTest' => [__DIR__ . '/../../../Resources/packages/phpcm'],
+                'TestValidators' => [__DIR__ . '/../../../Resources/packages/phpcm'],
+            ],
+            'class' => '\TestValidators\TestServiceClient',
+            'checkInterfaceExist' => false,
+            'useApcu' => true,
+            'apcuPrefix' => self::APCU_PREFIX,
+        ];
+    }
+}
+
+namespace Thrift\ClassLoader;
+
+use Test\Thrift\Unit\Lib\ClassLoader\ThriftClassLoaderTest;
+
+if (!function_exists('apcu_fetch')) {
+    {
+        function apcu_fetch($key, &$success = null)
+        {
+            if (strpos($key, ThriftClassLoaderTest::APCU_PREFIX) === false) {
+                throw new \Exception('apcu_fetch error, invalid key');
+            }
+
+            return false;
+        }
+
+        function apcu_store($key, $var, $ttl = 0)
+        {
+            if (strpos($key, ThriftClassLoaderTest::APCU_PREFIX) === false) {
+                throw new \Exception('apcu_store error, invalid key');
+            }
+
+            return false;
+        }
+    };
+}
diff --git a/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php
new file mode 100644
index 0000000..5f7c2a2
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.protocol
+ */
+
+namespace Test\Thrift\Unit\Lib\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Factory\TBinaryProtocolFactory;
+use Thrift\Protocol\TBinaryProtocol;
+use Thrift\Transport\TTransport;
+
+class TBinaryProtocolFactoryTest extends TestCase
+{
+    /**
+     * @dataProvider getProtocolDataProvider
+     * @param bool $strictRead
+     * @param bool $strictWrite
+     * @return void
+     */
+    public function testGetProtocol(
+        $strictRead,
+        $strictWrite
+    ) {
+        $transport = $this->createMock(TTransport::class);
+        $factory = new TBinaryProtocolFactory($strictRead, $strictWrite);
+        $protocol = $factory->getProtocol($transport);
+
+        $this->assertInstanceOf(TBinaryProtocol::class, $protocol);
+
+        $ref = new \ReflectionClass($protocol);
+        $refStrictRead = $ref->getProperty('strictRead_');
+        $refStrictRead->setAccessible(true);
+        $refStrictWrite = $ref->getProperty('strictWrite_');
+        $refStrictWrite->setAccessible(true);
+        $refTrans = $ref->getProperty('trans_');
+        $refTrans->setAccessible(true);
+
+        $this->assertEquals($strictRead, $refStrictRead->getValue($protocol));
+        $this->assertEquals($strictWrite, $refStrictWrite->getValue($protocol));
+        $this->assertSame($transport, $refTrans->getValue($protocol));
+    }
+
+    public function getProtocolDataProvider()
+    {
+        yield 'allTrue' => [
+            'strictRead' => true,
+            'strictWrite' => true,
+        ];
+        yield 'allFalse' => [
+            'strictRead' => false,
+            'strictWrite' => false,
+        ];
+        yield 'strictReadTrue' => [
+            'strictRead' => true,
+            'strictWrite' => false,
+        ];
+        yield 'strictWriteTrue' => [
+            'strictRead' => false,
+            'strictWrite' => true,
+        ];
+    }
+}
diff --git a/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php
new file mode 100644
index 0000000..f81e789
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.protocol
+ */
+
+namespace Test\Thrift\Unit\Lib\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Factory\TCompactProtocolFactory;
+use Thrift\Protocol\TCompactProtocol;
+use Thrift\Transport\TTransport;
+
+class TCompactProtocolFactoryTest extends TestCase
+{
+    /**
+     * @return void
+     */
+    public function testGetProtocol()
+    {
+        $transport = $this->createMock(TTransport::class);
+        $factory = new TCompactProtocolFactory();
+        $protocol = $factory->getProtocol($transport);
+
+        $this->assertInstanceOf(TCompactProtocol::class, $protocol);
+
+        $ref = new \ReflectionClass($protocol);
+        $refTrans = $ref->getProperty('trans_');
+        $refTrans->setAccessible(true);
+
+        $this->assertSame($transport, $refTrans->getValue($protocol));
+    }
+}
diff --git a/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php
new file mode 100644
index 0000000..2cb32d8
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.protocol
+ */
+
+namespace Test\Thrift\Unit\Lib\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Factory\TFramedTransportFactory;
+use Thrift\Transport\TFramedTransport;
+use Thrift\Transport\TTransport;
+
+class TFramedTransportFactoryTest extends TestCase
+{
+    /**
+     * @return void
+     */
+    public function testGetTransport()
+    {
+        $transport = $this->createMock(TTransport::class);
+        $factory = new TFramedTransportFactory();
+        $framedTransport = $factory::getTransport($transport);
+
+        $this->assertInstanceOf(TFramedTransport::class, $framedTransport);
+
+        $ref = new \ReflectionClass($framedTransport);
+        $refRead = $ref->getProperty('read_');
+        $refRead->setAccessible(true);
+        $refWrite = $ref->getProperty('write_');
+        $refWrite->setAccessible(true);
+        $refTrans = $ref->getProperty('transport_');
+        $refTrans->setAccessible(true);
+
+        $this->assertTrue($refRead->getValue($framedTransport));
+        $this->assertTrue($refWrite->getValue($framedTransport));
+        $this->assertSame($transport, $refTrans->getValue($framedTransport));
+    }
+}
diff --git a/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php
new file mode 100644
index 0000000..0685af7
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.protocol
+ */
+
+namespace Test\Thrift\Unit\Lib\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Factory\TJSONProtocolFactory;
+use Thrift\Protocol\TJSONProtocol;
+use Thrift\Transport\TTransport;
+
+class TJSONProtocolFactoryTest extends TestCase
+{
+    /**
+     * @return void
+     */
+    public function testGetProtocol()
+    {
+        $transport = $this->createMock(TTransport::class);
+        $factory = new TJSONProtocolFactory();
+        $protocol = $factory->getProtocol($transport);
+
+        $this->assertInstanceOf(TJSONProtocol::class, $protocol);
+
+        $ref = new \ReflectionClass($protocol);
+        $refTrans = $ref->getProperty('trans_');
+        $refTrans->setAccessible(true);
+
+        $this->assertSame($transport, $refTrans->getValue($protocol));
+    }
+}
diff --git a/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php
new file mode 100644
index 0000000..eb4df44
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.protocol
+ */
+
+namespace Test\Thrift\Unit\Lib\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Factory\TStringFuncFactory;
+use Thrift\StringFunc\Core;
+use Thrift\StringFunc\Mbstring;
+use Thrift\StringFunc\TStringFunc;
+
+class TStringFuncFactoryTest extends TestCase
+{
+    /**
+     * @return void
+     */
+    public function testCreate()
+    {
+        $factory = new TStringFuncFactory();
+        $stringFunc = $factory::create();
+        $this->assertInstanceOf(TStringFunc::class, $stringFunc);
+        $this->assertInstanceOf(Mbstring::class, $stringFunc);
+
+        /**
+         * it is a hack to nullable the instance of TStringFuncFactory, and get a new instance based on the new ini_get value
+         */
+        $ref = new \ReflectionClass($factory);
+        $refInstance = $ref->getProperty('_instance');
+        $refInstance->setAccessible(true);
+        $refInstance->setValue($factory, null);
+
+        $stringFunc = $factory::create();
+        $this->assertInstanceOf(TStringFunc::class, $stringFunc);
+        $this->assertInstanceOf(Core::class, $stringFunc);
+    }
+}
+
+
+namespace Thrift\Factory;
+
+function ini_get($key)
+{
+    static $count = 0;
+    if ($key === 'mbstring.func_overload') {
+        if ($count === 0) {
+            $count++;
+            return 2;
+        } else {
+            return 0;
+        }
+    } else {
+        return \ini_get($key);
+    }
+}
diff --git a/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php
new file mode 100644
index 0000000..da91b64
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * @package thrift.protocol
+ */
+
+namespace Test\Thrift\Unit\Lib\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Factory\TTransportFactory;
+use Thrift\Transport\TTransport;
+
+class TTransportFactoryTest extends TestCase
+{
+    /**
+     * @return void
+     */
+    public function testGetTransport()
+    {
+        $transport = $this->createMock(TTransport::class);
+        $factory = new TTransportFactory();
+        $result = $factory::getTransport($transport);
+
+        $this->assertSame($transport, $result);
+    }
+}
diff --git a/lib/php/test/Unit/Lib/StringFunc/CoreTest.php b/lib/php/test/Unit/Lib/StringFunc/CoreTest.php
new file mode 100644
index 0000000..73ebbfd
--- /dev/null
+++ b/lib/php/test/Unit/Lib/StringFunc/CoreTest.php
@@ -0,0 +1,215 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace Unit\Lib\StringFunc;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\StringFunc\Core;
+
+class CoreTest extends TestCase
+{
+    /**
+     * @dataProvider substrDataProvider
+     */
+    public function testSubstr(
+        $expected,
+        $str,
+        $start = 0,
+        $length = null
+    ) {
+        $core = new Core();
+        $this->assertEquals($expected, $core->substr($str, $start, $length));
+    }
+
+    /**
+     * @dataProvider strlenDataProvider
+     */
+    public function testStrlen(
+        $expectedLength,
+        $str
+    ) {
+        $core = new Core();
+        $this->assertEquals($expectedLength, $core->strlen($str));
+    }
+
+    public function substrDataProvider()
+    {
+        yield 'Afrikaans' => [
+            'expected' => 'Afrikaans',
+            'str' => 'Afrikaans',
+        ];
+        yield 'Alemannisch' => [
+            'expected' => 'Alemannisch',
+            'str' => 'Alemannisch',
+        ];
+        yield 'Aragonés' => [
+            'expected' => 'Aragonés',
+            'str' => 'Aragonés',
+        ];
+        yield 'العربية' => [
+            'expected' => 'العربية',
+            'str' => 'العربية',
+        ];
+        yield 'مصرى' => [
+            'expected' => 'مصرى',
+            'str' => 'مصرى',
+        ];
+        yield 'മലയാളം' => [
+            'expected' => 'മലയാളം',
+            'str' => 'മലയാളം',
+        ];
+        yield 'Slovenščina' => [
+            'expected' => 'Slovenščina',
+            'str' => 'Slovenščina',
+        ];
+        yield 'Українська' => [
+            'expected' => 'Українська',
+            'str' => 'Українська',
+        ];
+        yield 'اردو' => [
+            'expected' => 'اردو',
+            'str' => 'اردو',
+        ];
+        yield '中文' => [
+            'expected' => '中文',
+            'str' => '中文',
+        ];
+        yield '粵語' => [
+            'expected' => '粵語',
+            'str' => '粵語',
+        ];
+        yield 'Afrikaans_SUB' => [
+            'expected' => 'rikaan',
+            'str' => 'Afrikaans',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'Alemannisch_SUB' => [
+            'expected' => 'emanni',
+            'str' => 'Alemannisch',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'Aragonés_SUB' => [
+            'expected' => 'agoné',
+            'str' => 'Aragonés',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'العربية_SUB' => [
+            'expected' => 'لعر',
+            'str' => 'العربية',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'مصرى_SUB' => [
+            'expected' => 'صرى',
+            'str' => 'مصرى',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'മലയാളം_SUB' => [
+            'expected' => 'ലയ',
+            'str' => 'മലയാളം',
+            'start' => 3,
+            'length' => 6,
+        ];
+        yield 'Slovenščina_SUB' => [
+            'expected' => 'ovenš',
+            'str' => 'Slovenščina',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'Українська_SUB' => [
+            'expected' => 'кра',
+            'str' => 'Українська',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'اردو_SUB' => [
+            'expected' => 'ردو',
+            'str' => 'اردو',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield '中文_SUB' => [
+            'expected' => '文',
+            'str' => '中文',
+            'start' => 3,
+            'length' => 3,
+        ];
+        yield '粵語_SUB' => [
+            'expected' => '語',
+            'str' => '粵語',
+            'start' => 3,
+            'length' => 3,
+        ];
+    }
+
+    public function strlenDataProvider()
+    {
+        yield 'Afrikaans' => [
+            'expectedLength' => 9,
+            'str' => 'Afrikaans',
+        ];
+        yield 'Alemannisch' => [
+            'expectedLength' => 11,
+            'str' => 'Alemannisch',
+        ];
+        yield 'Aragonés' => [
+            'expectedLength' => 9,
+            'str' => 'Aragonés',
+        ];
+        yield 'العربية' => [
+            'expectedLength' => 14,
+            'str' => 'العربية',
+        ];
+        yield 'مصرى' => [
+            'expectedLength' => 8,
+            'str' => 'مصرى',
+        ];
+        yield 'മലയാളം' => [
+            'expectedLength' => 18,
+            'str' => 'മലയാളം',
+        ];
+        yield 'Slovenščina' => [
+            'expectedLength' => 13,
+            'str' => 'Slovenščina',
+        ];
+        yield 'Українська' => [
+            'expectedLength' => 20,
+            'str' => 'Українська',
+        ];
+        yield 'اردو' => [
+            'expectedLength' => 8,
+            'str' => 'اردو',
+        ];
+        yield '中文' => [
+            'expectedLength' => 6,
+            'str' => '中文',
+        ];
+        yield '粵語' => [
+            'expectedLength' => 6,
+            'str' => '粵語',
+        ];
+    }
+}
diff --git a/lib/php/test/Unit/Lib/StringFunc/MbStringTest.php b/lib/php/test/Unit/Lib/StringFunc/MbStringTest.php
new file mode 100644
index 0000000..5827f91
--- /dev/null
+++ b/lib/php/test/Unit/Lib/StringFunc/MbStringTest.php
@@ -0,0 +1,215 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace Unit\Lib\StringFunc;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\StringFunc\Mbstring;
+
+class MbStringTest extends TestCase
+{
+    /**
+     * @dataProvider substrDataProvider
+     */
+    public function testSubstr(
+        $expected,
+        $str,
+        $start = 0,
+        $length = null
+    ) {
+        $core = new Mbstring();
+        $this->assertEquals($expected, $core->substr($str, $start, $length));
+    }
+
+    /**
+     * @dataProvider strlenDataProvider
+     */
+    public function testStrlen(
+        $expectedLength,
+        $str
+    ) {
+        $core = new Mbstring();
+        $this->assertEquals($expectedLength, $core->strlen($str));
+    }
+
+    public function substrDataProvider()
+    {
+        yield 'Afrikaans' => [
+            'expected' => 'Afrikaans',
+            'str' => 'Afrikaans',
+        ];
+        yield 'Alemannisch' => [
+            'expected' => 'Alemannisch',
+            'str' => 'Alemannisch',
+        ];
+        yield 'Aragonés' => [
+            'expected' => 'Aragonés',
+            'str' => 'Aragonés',
+        ];
+        yield 'العربية' => [
+            'expected' => 'العربية',
+            'str' => 'العربية',
+        ];
+        yield 'مصرى' => [
+            'expected' => 'مصرى',
+            'str' => 'مصرى',
+        ];
+        yield 'മലയാളം' => [
+            'expected' => 'മലയാളം',
+            'str' => 'മലയാളം',
+        ];
+        yield 'Slovenščina' => [
+            'expected' => 'Slovenščina',
+            'str' => 'Slovenščina',
+        ];
+        yield 'Українська' => [
+            'expected' => 'Українська',
+            'str' => 'Українська',
+        ];
+        yield 'اردو' => [
+            'expected' => 'اردو',
+            'str' => 'اردو',
+        ];
+        yield '中文' => [
+            'expected' => '中文',
+            'str' => '中文',
+        ];
+        yield '粵語' => [
+            'expected' => '粵語',
+            'str' => '粵語',
+        ];
+        yield 'Afrikaans_SUB' => [
+            'expected' => 'rikaan',
+            'str' => 'Afrikaans',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'Alemannisch_SUB' => [
+            'expected' => 'emanni',
+            'str' => 'Alemannisch',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'Aragonés_SUB' => [
+            'expected' => 'agoné',
+            'str' => 'Aragonés',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'العربية_SUB' => [
+            'expected' => 'لعر',
+            'str' => 'العربية',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'مصرى_SUB' => [
+            'expected' => 'صرى',
+            'str' => 'مصرى',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'മലയാളം_SUB' => [
+            'expected' => 'ലയ',
+            'str' => 'മലയാളം',
+            'start' => 3,
+            'length' => 6,
+        ];
+        yield 'Slovenščina_SUB' => [
+            'expected' => 'ovenš',
+            'str' => 'Slovenščina',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'Українська_SUB' => [
+            'expected' => 'кра',
+            'str' => 'Українська',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield 'اردو_SUB' => [
+            'expected' => 'ردو',
+            'str' => 'اردو',
+            'start' => 2,
+            'length' => 6,
+        ];
+        yield '中文_SUB' => [
+            'expected' => '文',
+            'str' => '中文',
+            'start' => 3,
+            'length' => 3,
+        ];
+        yield '粵語_SUB' => [
+            'expected' => '語',
+            'str' => '粵語',
+            'start' => 3,
+            'length' => 3,
+        ];
+    }
+
+    public function strlenDataProvider()
+    {
+        yield 'Afrikaans' => [
+            'expectedLength' => 9,
+            'str' => 'Afrikaans',
+        ];
+        yield 'Alemannisch' => [
+            'expectedLength' => 11,
+            'str' => 'Alemannisch',
+        ];
+        yield 'Aragonés' => [
+            'expectedLength' => 9,
+            'str' => 'Aragonés',
+        ];
+        yield 'العربية' => [
+            'expectedLength' => 14,
+            'str' => 'العربية',
+        ];
+        yield 'مصرى' => [
+            'expectedLength' => 8,
+            'str' => 'مصرى',
+        ];
+        yield 'മലയാളം' => [
+            'expectedLength' => 18,
+            'str' => 'മലയാളം',
+        ];
+        yield 'Slovenščina' => [
+            'expectedLength' => 13,
+            'str' => 'Slovenščina',
+        ];
+        yield 'Українська' => [
+            'expectedLength' => 20,
+            'str' => 'Українська',
+        ];
+        yield 'اردو' => [
+            'expectedLength' => 8,
+            'str' => 'اردو',
+        ];
+        yield '中文' => [
+            'expectedLength' => 6,
+            'str' => '中文',
+        ];
+        yield '粵語' => [
+            'expectedLength' => 6,
+            'str' => '粵語',
+        ];
+    }
+}