Seamless migration of original projects to new services through PHP wrapper


For the sake of performance and security, local file reading and writing and external data capture are disabled on the company’s platform. Accordingly, we provide corresponding services to do the same thing. The interface of the new service is different from the original

The program specially developed for our platform certainly has no problem, but there are a lot of existing programs and open source projects, which are faced with complicated migration work


In fact, PHP has supported wrapper since PHP 4.3, which means that users can customize and overload protocols

Just usestream_wrapper_register Function can register a protocol. PHP will call back the related functions for the related operations of the protocol

The manual gives an example. It registers a protocol called VaR, and then calls back the operation of this protocolVariableStream classThe method defined inside

varname = $url["host"];
$this->position = 0;
return true;

function stream_read($count)
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return $ret;

function stream_write($data)
$left = substr($GLOBALS[$this->varname], 0, $this->position);
$right = substr($GLOBALS[$this->varname], $this->position + strlen($data));
$GLOBALS[$this->varname] = $left . $data . $right;
$this->position += strlen($data);
return strlen($data);

function stream_tell()
return $this->position;

function stream_eof()
return $this->position >= strlen($GLOBALS[$this->varname]);

function stream_seek($offset, $whence)


switch ($whence) {

case SEEK_SET:

if ($offset < strlen($GLOBALS[$this->varname]) && $offset >= 0) {

$this->position = $offset;

return true;

} else {

return false;



case SEEK_CUR:

if ($offset >= 0) {

$this->position += $offset;

return true;

} else {

return false;



case SEEK_END:

if (strlen($GLOBALS[$this->varname]) + $offset >= 0) {

$this->position = strlen($GLOBALS[$this->varname]) + $offset;

return true;

} else {

return false;




return false;




stream_wrapper_register("var", "VariableStream")

or die("Failed to register protocol");

$myvar = "";

$fp = fopen("var://myvar", "r+");

fwrite($fp, "line1\n");

fwrite($fp, "line2\n");

fwrite($fp, "line3\n");


while (!feof($fp)) {

echo fgets($fp);





The list of interfaces that can be implemented in callback class is here:

Some problems needing attention


First of all,wrapper classIn particular, its constructor is not called every time. Only when your operation triggers the stream_ Open related operations, such as you usefile_get_contentsWhen you trigger stream independent functions, such as file_ Exists triggers the URL_ Stat method, the constructor will not be called at this time

Read implementation

There are concepts such as position and seek in wrapper, but many services actually read all data at one time. This can be found instream_openRead it back at one time and put it into an attribute. Later, you can directly operate the data stored in the attribute in seek and tell

url_ Implementation of stat

In the implementation of wrapper class, the URL_ The implementation of stat is a difficulty. URL must be implemented correctly_ Stat can makeis_writableAnd is_ Readable and other functions for querying meta information of files work normally

And we need to forge these values for our virtual device. Take MC as an example, I’ll give you some reference data

url_ Stat should return an array with 13 items as follows:

Dev device number – write 0

Ino inode number – write 0

Mode file mode – this is the permission control symbol of the file, which will be explained in detail later

Nlink link – write 0

Uid uid – POSIX on Linux_ get_ Uid can be obtained, 0 on windows

GID GID – POSIX on Linux_ get_ GID can be retrieved, 0 on windows

Rdev device type – has a value when it is an inode device

Size file size

The last read time format of atime is UNIX timestamp

Mtime last write time

CTime creation time

Blksize block size of filesystem IO

Blocks number of 512 byte blocks allocated

The value of mode must be written correctly

If it is a file, the value is

0100000 + file permission; for example, 0100000 + 0777;

If it is a directory, the value is

040000 + directory permission, such as 0400000 + 0777;

Standard protocols can be overloaded

According to the actual test, using thestream_wrapper_unregisterYou can uninstall the built-in protocols such as HTTP. This is convenient for us to completely and seamlessly replace some operations of users, such asfile_get_contents(‘')To our own services

Knowledge point supplement:

Implementation of PHP wrapper


Make a wrapper of thrift client to realize the retrial logic for the server.

[key points]

1. Wrapper is as convenient as client.

2. When a server fails, you can randomly select another server to try again.

3. Several key features of PHP:__ Call () (magic function, which will be called when the accessed object function does not exist), reflectionclass reflects the class and its member function newinstanceargs, call_ user_ func_ Array callback function.

Let’s look at the code directly (written by a great man, not me)

#!/usr/bin/env php
namespace wrapper;
require_once '/usr/local/Cellar/thrift/0.9.1/Thrift/ClassLoader/ThriftClassLoader.php';
use Thrift\ClassLoader\ThriftClassLoader;
$GEN_DIR = realpath(dirname(__FILE__).'/..').'/gen-php';
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift', '/usr/local/Cellar/thrift/0.9.1/');
$loader->registerDefinition('xiaoju', $GEN_DIR);
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use Thrift\Transport\THttpClient;
use Thrift\Transport\TBufferedTransport;
use Thrift\Exception\TException;
class RetryWrapper {
  public function __construct($classname, $hosts) {
    $this->clazz = new \ReflectionClass($classname);
    $this->hosts = $hosts;
  public function __call($method, $args) {
    foreach ($this->hosts as $key => $host) {
      try {
        return $this->inner_call($host, $method, $args);
      } catch (TException $ex) {
        $msg = $ex->getMessage();
        if (!strstr($msg, 'TSocket')) {
          throw $ex;
    throw new TException("all server down!");
  public function inner_call($host, $method, $args) {
    $tmp = explode(":", $host);
    $socket = new TSocket($tmp[0], (int)$tmp[1]);
    $transport = new TBufferedTransport($socket, 1024, 1024);
    $protocol = new TBinaryProtocol($transport);
    $client = $this->clazz->newInstanceArgs(array($protocol));
    $result = call_user_func_array(array($client, $method), $args);
    return $result;
$hosts = array('localhost:9090', 'localhost:9091');
$wrapper = new RetryWrapper("\xxx\xx\MessageServiceClient", $hosts, 3);
$data = array('businessId' => 300100001, 'phones' => array('2','2','3'), 'message' => 'asdfqer') ;
$message = new \xxx\xx\Message($data);
print $wrapper->sendMessage($message);
print "\n";


Here is the article about how to seamlessly migrate the original project to the new service through PHP wrapper. For more information about PHP wrapper’s migration of new service, please search previous articles of developer or continue to browse the following articles. I hope you can support developer more in the future!