[THRIFT-5757] Unit tests for php lib
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' => '粵語',
+        ];
+    }
+}