├── .gitignore ├── thrift_protocol.ini ├── coding_standards.md ├── src ├── ext │ └── thrift_protocol │ │ ├── config.w32 │ │ ├── php_thrift_protocol.h │ │ └── config.m4 ├── autoload.php └── TStringUtils.php ├── lib ├── Factory │ ├── TTransportFactory.php │ ├── TProtocolFactory.php │ ├── TJSONProtocolFactory.php │ ├── TCompactProtocolFactory.php │ ├── TBinaryProtocolFactory.php │ └── TStringFuncFactory.php ├── StringFunc │ ├── TStringFunc.php │ ├── Core.php │ └── Mbstring.php ├── Type │ ├── TMessageType.php │ ├── TType.php │ └── TConstant.php ├── Protocol │ ├── SimpleJSON │ │ ├── Context.php │ │ ├── CollectionMapKeyException.php │ │ ├── ListContext.php │ │ ├── MapContext.php │ │ └── StructContext.php │ ├── JSON │ │ ├── BaseContext.php │ │ ├── ListContext.php │ │ ├── LookaheadReader.php │ │ └── PairContext.php │ ├── TBinaryProtocolAccelerated.php │ ├── TMultiplexedProtocol.php │ ├── TProtocolDecorator.php │ └── TSimpleJSONProtocol.php ├── Server │ ├── TServerTransport.php │ ├── TSimpleServer.php │ ├── TServer.php │ ├── TSSLServerSocket.php │ ├── TServerSocket.php │ └── TForkingServer.php ├── Exception │ ├── TTransportException.php │ ├── TProtocolException.php │ └── TApplicationException.php ├── Transport │ ├── TNullTransport.php │ ├── TTransport.php │ ├── TMemoryBuffer.php │ ├── TPhpStream.php │ ├── TSSLSocket.php │ ├── TFramedTransport.php │ ├── TBufferedTransport.php │ ├── THttpClient.php │ ├── TCurlClient.php │ ├── TSocketPool.php │ └── TSocket.php ├── StoredMessageProtocol.php ├── Serializer │ └── TBinarySerializer.php ├── TMultiplexedProcessor.php └── ClassLoader │ └── ThriftClassLoader.php ├── composer.json ├── test ├── TestValidators.thrift ├── Validator │ ├── ValidatorTest.php │ ├── ValidatorTestOop.php │ └── BaseValidatorTest.php ├── Makefile.am ├── Protocol │ ├── BinarySerializerTest.php │ └── TSimpleJSONProtocolTest.php ├── JsonSerialize │ └── JsonSerializeTest.php └── Fixtures.php ├── README.md ├── README.origin.md ├── README.apache.md └── Makefile.am /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | -------------------------------------------------------------------------------- /thrift_protocol.ini: -------------------------------------------------------------------------------- 1 | extension=thrift_protocol.so 2 | -------------------------------------------------------------------------------- /coding_standards.md: -------------------------------------------------------------------------------- 1 | ## PHP Coding Standards 2 | 3 | Please follow: 4 | * [Thrift General Coding Standards](/doc/coding_standards.md) 5 | * [PSR-2](http://www.php-fig.org/psr/psr-2/) 6 | -------------------------------------------------------------------------------- /src/ext/thrift_protocol/config.w32: -------------------------------------------------------------------------------- 1 | // $Id: config.w32 250404 2008-01-11 13:37:24Z rrichards $ 2 | // vim:ft=javascript 3 | 4 | ARG_WITH("thrift_protocol", "whether to enable the thrift_protocol extension", "yes"); 5 | 6 | if (PHP_THRIFT_PROTOCOL == "yes"){ 7 | EXTENSION("thrift_protocol", "php_thrift_protocol.cpp") 8 | } 9 | -------------------------------------------------------------------------------- /lib/Factory/TTransportFactory.php: -------------------------------------------------------------------------------- 1 | =5.5.0" 18 | }, 19 | "autoload": { 20 | "psr-0": { 21 | "Thrift": "lib" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/StringFunc/TStringFunc.php: -------------------------------------------------------------------------------- 1 | acceptImpl(); 49 | 50 | if ($transport == null) { 51 | throw new TTransportException("accept() may not return NULL"); 52 | } 53 | 54 | return $transport; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/Factory/TProtocolFactory.php: -------------------------------------------------------------------------------- 1 | addPsr4('', __DIR__ . '/../packages/phpv'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/Protocol/SimpleJSON/ListContext.php: -------------------------------------------------------------------------------- 1 | p_ = $p; 35 | } 36 | 37 | public function write() 38 | { 39 | if ($this->first_) { 40 | $this->first_ = false; 41 | } else { 42 | $this->p_->getTransport()->write(TSimpleJSONProtocol::COMMA); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Validator/ValidatorTestOop.php: -------------------------------------------------------------------------------- 1 | addPsr4('', __DIR__ . '/../packages/phpvo'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/Protocol/SimpleJSON/MapContext.php: -------------------------------------------------------------------------------- 1 | isKey = !$this->isKey; 39 | } 40 | 41 | public function isMapKey() 42 | { 43 | // we want to coerce map keys to json strings regardless 44 | // of their type 45 | return $this->isKey; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/Type/TType.php: -------------------------------------------------------------------------------- 1 | strictRead_ = $strictRead; 38 | $this->strictWrite_ = $strictWrite; 39 | } 40 | 41 | public function getProtocol($trans) 42 | { 43 | return new TBinaryProtocol($trans, $this->strictRead_, $this->strictWrite_); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/Type/TConstant.php: -------------------------------------------------------------------------------- 1 | p_ = $p; 35 | } 36 | 37 | public function write() 38 | { 39 | if ($this->first_) { 40 | $this->first_ = false; 41 | } else { 42 | $this->p_->getTransport()->write(TJSONProtocol::COMMA); 43 | } 44 | } 45 | 46 | public function read() 47 | { 48 | if ($this->first_) { 49 | $this->first_ = false; 50 | } else { 51 | $this->p_->readJSONSyntaxChar(TJSONProtocol::COMMA); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/StringFunc/Mbstring.php: -------------------------------------------------------------------------------- 1 | strlen($str) - $start; 37 | } 38 | 39 | return mb_substr($str, $start, $length, '8bit'); 40 | } 41 | 42 | public function strlen($str) 43 | { 44 | return mb_strlen($str, '8bit'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/Protocol/SimpleJSON/StructContext.php: -------------------------------------------------------------------------------- 1 | p_ = $p; 36 | } 37 | 38 | public function write() 39 | { 40 | if ($this->first_) { 41 | $this->first_ = false; 42 | $this->colon_ = true; 43 | } else { 44 | $this->p_->getTransport()->write( 45 | $this->colon_ ? 46 | TSimpleJSONProtocol::COLON : 47 | TSimpleJSONProtocol::COMMA 48 | ); 49 | $this->colon_ = !$this->colon_; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/Protocol/JSON/LookaheadReader.php: -------------------------------------------------------------------------------- 1 | p_ = $p; 34 | } 35 | 36 | public function read() 37 | { 38 | if ($this->hasData_) { 39 | $this->hasData_ = false; 40 | } else { 41 | $this->data_ = $this->p_->getTransport()->readAll(1); 42 | } 43 | 44 | return substr($this->data_, 0, 1); 45 | } 46 | 47 | public function peek() 48 | { 49 | if (!$this->hasData_) { 50 | $this->data_ = $this->p_->getTransport()->readAll(1); 51 | } 52 | 53 | $this->hasData_ = true; 54 | 55 | return substr($this->data_, 0, 1); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/Server/TSimpleServer.php: -------------------------------------------------------------------------------- 1 | transport_->listen(); 31 | 32 | while (!$this->stop_) { 33 | try { 34 | $transport = $this->transport_->accept(); 35 | 36 | if ($transport != null) { 37 | $inputTransport = $this->inputTransportFactory_->getTransport($transport); 38 | $outputTransport = $this->outputTransportFactory_->getTransport($transport); 39 | $inputProtocol = $this->inputProtocolFactory_->getProtocol($inputTransport); 40 | $outputProtocol = $this->outputProtocolFactory_->getProtocol($outputTransport); 41 | while ($this->processor_->process($inputProtocol, $outputProtocol)) { 42 | } 43 | } 44 | } catch (TTransportException $e) { 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * Stops the server running. Kills the transport 51 | * and then stops the main serving loop 52 | * 53 | * @return void 54 | */ 55 | public function stop() 56 | { 57 | $this->transport_->close(); 58 | $this->stop_ = true; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/StoredMessageProtocol.php: -------------------------------------------------------------------------------- 1 | fname_ = $fname; 43 | $this->mtype_ = $mtype; 44 | $this->rseqid_ = $rseqid; 45 | } 46 | 47 | public function readMessageBegin(&$name, &$type, &$seqid) 48 | { 49 | $name = $this->fname_; 50 | $type = $this->mtype_; 51 | $seqid = $this->rseqid_; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Makefile.am: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | 20 | PHPUNIT=php $(top_srcdir)/vendor/bin/phpunit 21 | 22 | stubs: ../../../test/ThriftTest.thrift TestValidators.thrift 23 | mkdir -p ./packages/php 24 | $(THRIFT) --gen php -r --out ./packages/php ../../../test/ThriftTest.thrift 25 | mkdir -p ./packages/phpv 26 | mkdir -p ./packages/phpvo 27 | mkdir -p ./packages/phpjs 28 | $(THRIFT) --gen php:validate -r --out ./packages/phpv TestValidators.thrift 29 | $(THRIFT) --gen php:validate,oop -r --out ./packages/phpvo TestValidators.thrift 30 | $(THRIFT) --gen php:json -r --out ./packages/phpjs TestValidators.thrift 31 | 32 | deps: $(top_srcdir)/composer.json 33 | composer install --working-dir=$(top_srcdir) 34 | 35 | all-local: deps 36 | 37 | check-json-serializer: deps stubs 38 | $(PHPUNIT) --log-junit=TEST-log-json-serializer.xml JsonSerialize/ 39 | 40 | check-validator: deps stubs 41 | $(PHPUNIT) --log-junit=TEST-log-validator.xml Validator/ 42 | 43 | check-protocol: deps stubs 44 | $(PHPUNIT) --log-junit=TEST-log-protocol.xml Protocol/ 45 | 46 | check: deps stubs \ 47 | check-protocol \ 48 | check-validator \ 49 | check-json-serializer 50 | 51 | distclean-local: 52 | 53 | clean-local: 54 | $(RM) -r ./packages 55 | $(RM) TEST-*.xml 56 | -------------------------------------------------------------------------------- /lib/Protocol/JSON/PairContext.php: -------------------------------------------------------------------------------- 1 | p_ = $p; 36 | } 37 | 38 | public function write() 39 | { 40 | if ($this->first_) { 41 | $this->first_ = false; 42 | $this->colon_ = true; 43 | } else { 44 | $this->p_->getTransport()->write($this->colon_ ? TJSONProtocol::COLON : TJSONProtocol::COMMA); 45 | $this->colon_ = !$this->colon_; 46 | } 47 | } 48 | 49 | public function read() 50 | { 51 | if ($this->first_) { 52 | $this->first_ = false; 53 | $this->colon_ = true; 54 | } else { 55 | $this->p_->readJSONSyntaxChar($this->colon_ ? TJSONProtocol::COLON : TJSONProtocol::COMMA); 56 | $this->colon_ = !$this->colon_; 57 | } 58 | } 59 | 60 | public function escapeNum() 61 | { 62 | return $this->colon_; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | addPsr4('', __DIR__ . '/../packages/php'); 47 | } 48 | 49 | /** 50 | * We try to serialize and deserialize a random object to make sure no exceptions are thrown. 51 | * @see THRIFT-1579 52 | */ 53 | public function testBinarySerializer() 54 | { 55 | $struct = new \ThriftTest\Xtruct(array('string_thing' => 'abc')); 56 | $serialized = TBinarySerializer::serialize($struct, 'ThriftTest\\Xtruct'); 57 | $deserialized = TBinarySerializer::deserialize($serialized, 'ThriftTest\\Xtruct'); 58 | $this->assertEquals($struct, $deserialized); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/Factory/TStringFuncFactory.php: -------------------------------------------------------------------------------- 1 | registerNamespace('namespace', '')` instead of `$thriftClassLoader->registerDefinition('namespace', '')`. 61 | -------------------------------------------------------------------------------- /README.origin.md: -------------------------------------------------------------------------------- 1 | Thrift PHP Software Library 2 | 3 | # License 4 | 5 | Licensed to the Apache Software Foundation (ASF) under one 6 | or more contributor license agreements. See the NOTICE file 7 | distributed with this work for additional information 8 | regarding copyright ownership. The ASF licenses this file 9 | to you under the Apache License, Version 2.0 (the 10 | "License"); you may not use this file except in compliance 11 | with the License. You may obtain a copy of the License at 12 | 13 | http://www.apache.org/licenses/LICENSE-2.0 14 | 15 | Unless required by applicable law or agreed to in writing, 16 | software distributed under the License is distributed on an 17 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | KIND, either express or implied. See the License for the 19 | specific language governing permissions and limitations 20 | under the License. 21 | 22 | # Using Thrift with PHP 23 | 24 | Thrift requires PHP 5. Thrift makes as few assumptions about your PHP 25 | environment as possible while trying to make some more advanced PHP 26 | features (i.e. APC cacheing using asbolute path URLs) as simple as possible. 27 | 28 | To use Thrift in your PHP codebase, take the following steps: 29 | 30 | 1. Copy all of thrift/lib/php/lib into your PHP codebase 31 | 2. Configure Symfony Autoloader (or whatever you usually use) 32 | 33 | After that, you have to manually include the Thrift package 34 | created by the compiler: 35 | 36 | ``` 37 | require_once 'packages/Service/Service.php'; 38 | require_once 'packages/Service/Types.php'; 39 | ``` 40 | 41 | # Dependencies 42 | 43 | PHP_INT_SIZE 44 | 45 | This built-in signals whether your architecture is 32 or 64 bit and is 46 | used by the TBinaryProtocol to properly use pack() and unpack() to 47 | serialize data. 48 | 49 | apc_fetch(), apc_store() 50 | 51 | APC cache is used by the TSocketPool class. If you do not have APC installed, 52 | Thrift will fill in null stub function definitions. 53 | 54 | # Breaking Changes 55 | 56 | ## 0.12.0 57 | 58 | 1. [PSR-4](https://www.php-fig.org/psr/psr-4/) loader is now the default. If you want to use class maps instead, use `-gen php:classmap`. 59 | 60 | 2. If using PSR-4, use `$thriftClassLoader->registerNamespace('namespace', '')` instead of `$thriftClassLoader->registerDefinition('namespace', '')`. 61 | -------------------------------------------------------------------------------- /README.apache.md: -------------------------------------------------------------------------------- 1 | Thrift PHP/Apache Integration 2 | 3 | License 4 | ======= 5 | 6 | Licensed to the Apache Software Foundation (ASF) under one 7 | or more contributor license agreements. See the NOTICE file 8 | distributed with this work for additional information 9 | regarding copyright ownership. The ASF licenses this file 10 | to you under the Apache License, Version 2.0 (the 11 | "License"); you may not use this file except in compliance 12 | with the License. You may obtain a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, 17 | software distributed under the License is distributed on an 18 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | KIND, either express or implied. See the License for the 20 | specific language governing permissions and limitations 21 | under the License. 22 | 23 | Building PHP Thrift Services with Apache 24 | ======================================== 25 | 26 | Thrift can be embedded in the Apache webserver with PHP installed. Sample 27 | code is provided below. Note that to make requests to this type of server 28 | you must use a THttpClient transport. 29 | 30 | Sample Code 31 | =========== 32 | 33 | registerNamespace('Thrift', $THRIFT_ROOT); 49 | $loader->registerDefinition('Thrift', $THRIFT_ROOT . '/packages'); 50 | $loader->register(); 51 | 52 | use Thrift\Transport\TPhpStream; 53 | use Thrift\Protocol\TBinaryProtocol; 54 | 55 | /** 56 | * Example of how to build a Thrift server in Apache/PHP 57 | */ 58 | 59 | class ServiceHandler implements ServiceIf { 60 | // Implement your interface and methods here 61 | } 62 | 63 | header('Content-Type: application/x-thrift'); 64 | 65 | $handler = new ServiceHandler(); 66 | $processor = new ServiceProcessor($handler); 67 | 68 | // Use the TPhpStream transport to read/write directly from HTTP 69 | $transport = new TPhpStream(TPhpStream::MODE_R | TPhpStream::MODE_W); 70 | $protocol = new TBinaryProtocol($transport); 71 | 72 | $transport->open(); 73 | $processor->process($protocol, $protocol); 74 | $transport->close(); 75 | -------------------------------------------------------------------------------- /src/TStringUtils.php: -------------------------------------------------------------------------------- 1 | strlen($str) - $start; 40 | } 41 | 42 | return mb_substr($str, $start, $length, '8bit'); 43 | } 44 | 45 | public function strlen($str) 46 | { 47 | return mb_strlen($str, '8bit'); 48 | } 49 | } 50 | 51 | class TStringFuncFactory 52 | { 53 | private static $_instance; 54 | 55 | /** 56 | * Get the Singleton instance of TStringFunc implementation that is 57 | * compatible with the current system's mbstring.func_overload settings. 58 | * 59 | * @return TStringFunc 60 | */ 61 | public static function create() 62 | { 63 | if (!self::$_instance) { 64 | self::_setInstance(); 65 | } 66 | 67 | return self::$_instance; 68 | } 69 | 70 | private static function _setInstance() 71 | { 72 | /** 73 | * Cannot use str* functions for byte counting because multibyte 74 | * characters will be read a single bytes. 75 | * 76 | * See: http://us.php.net/manual/en/mbstring.overload.php 77 | */ 78 | if (ini_get('mbstring.func_overload') & 2) { 79 | self::$_instance = new TStringFunc_Mbstring(); 80 | } 81 | /** 82 | * mbstring is not installed or does not have function overloading 83 | * of the str* functions enabled so use PHP core str* functions for 84 | * byte counting. 85 | */ 86 | else { 87 | self::$_instance = new TStringFunc_Core(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/Exception/TApplicationException.php: -------------------------------------------------------------------------------- 1 | array('var' => 'message', 31 | 'type' => TType::STRING), 32 | 2 => array('var' => 'code', 33 | 'type' => TType::I32)); 34 | 35 | const UNKNOWN = 0; 36 | const UNKNOWN_METHOD = 1; 37 | const INVALID_MESSAGE_TYPE = 2; 38 | const WRONG_METHOD_NAME = 3; 39 | const BAD_SEQUENCE_ID = 4; 40 | const MISSING_RESULT = 5; 41 | const INTERNAL_ERROR = 6; 42 | const PROTOCOL_ERROR = 7; 43 | const INVALID_TRANSFORM = 8; 44 | const INVALID_PROTOCOL = 9; 45 | const UNSUPPORTED_CLIENT_TYPE = 10; 46 | 47 | public function __construct($message = null, $code = 0) 48 | { 49 | parent::__construct($message, $code); 50 | } 51 | 52 | public function read($output) 53 | { 54 | return $this->_read('TApplicationException', self::$_TSPEC, $output); 55 | } 56 | 57 | public function write($output) 58 | { 59 | $xfer = 0; 60 | $xfer += $output->writeStructBegin('TApplicationException'); 61 | if ($message = $this->getMessage()) { 62 | $xfer += $output->writeFieldBegin('message', TType::STRING, 1); 63 | $xfer += $output->writeString($message); 64 | $xfer += $output->writeFieldEnd(); 65 | } 66 | if ($code = $this->getCode()) { 67 | $xfer += $output->writeFieldBegin('type', TType::I32, 2); 68 | $xfer += $output->writeI32($code); 69 | $xfer += $output->writeFieldEnd(); 70 | } 71 | $xfer += $output->writeFieldStop(); 72 | $xfer += $output->writeStructEnd(); 73 | 74 | return $xfer; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/Server/TServer.php: -------------------------------------------------------------------------------- 1 | processor_ = $processor; 78 | $this->transport_ = $transport; 79 | $this->inputTransportFactory_ = $inputTransportFactory; 80 | $this->outputTransportFactory_ = $outputTransportFactory; 81 | $this->inputProtocolFactory_ = $inputProtocolFactory; 82 | $this->outputProtocolFactory_ = $outputProtocolFactory; 83 | } 84 | 85 | /** 86 | * Serves the server. This should never return 87 | * unless a problem permits it to do so or it 88 | * is interrupted intentionally 89 | * 90 | * @abstract 91 | * @return void 92 | */ 93 | abstract public function serve(); 94 | 95 | /** 96 | * Stops the server serving 97 | * 98 | * @abstract 99 | * @return void 100 | */ 101 | abstract public function stop(); 102 | } 103 | -------------------------------------------------------------------------------- /lib/Protocol/TBinaryProtocolAccelerated.php: -------------------------------------------------------------------------------- 1 | strictRead_; 61 | } 62 | 63 | public function isStrictWrite() 64 | { 65 | return $this->strictWrite_; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/Transport/TTransport.php: -------------------------------------------------------------------------------- 1 | read($len); 72 | 73 | $data = ''; 74 | $got = 0; 75 | while (($got = TStringFuncFactory::create()->strlen($data)) < $len) { 76 | $data .= $this->read($len - $got); 77 | } 78 | 79 | return $data; 80 | } 81 | 82 | /** 83 | * Writes the given data out. 84 | * 85 | * @param string $buf The data to write 86 | * @throws TTransportException if writing fails 87 | */ 88 | abstract public function write($buf); 89 | 90 | /** 91 | * Flushes any pending data out of a buffer 92 | * 93 | * @throws TTransportException if a writing error occurs 94 | */ 95 | public function flush() 96 | { 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/Server/TSSLServerSocket.php: -------------------------------------------------------------------------------- 1 | getSSLHost($host); 51 | parent::__construct($ssl_host, $port); 52 | $this->context_ = $context; 53 | } 54 | 55 | public function getSSLHost($host) 56 | { 57 | $transport_protocol_loc = strpos($host, "://"); 58 | if ($transport_protocol_loc === false) { 59 | $host = 'ssl://' . $host; 60 | } 61 | return $host; 62 | } 63 | 64 | /** 65 | * Opens a new socket server handle 66 | * 67 | * @return void 68 | */ 69 | public function listen() 70 | { 71 | $this->listener_ = @stream_socket_server( 72 | $this->host_ . ':' . $this->port_, 73 | $errno, 74 | $errstr, 75 | STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, 76 | $this->context_ 77 | ); 78 | } 79 | 80 | /** 81 | * Implementation of accept. If not client is accepted in the given time 82 | * 83 | * @return TSocket 84 | */ 85 | protected function acceptImpl() 86 | { 87 | $handle = @stream_socket_accept($this->listener_, $this->acceptTimeout_ / 1000.0); 88 | if (!$handle) { 89 | return null; 90 | } 91 | 92 | $socket = new TSSLSocket(); 93 | $socket->setHandle($handle); 94 | 95 | return $socket; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/Transport/TMemoryBuffer.php: -------------------------------------------------------------------------------- 1 | buf_ = $buf; 45 | } 46 | 47 | protected $buf_ = ''; 48 | 49 | public function isOpen() 50 | { 51 | return true; 52 | } 53 | 54 | public function open() 55 | { 56 | } 57 | 58 | public function close() 59 | { 60 | } 61 | 62 | public function write($buf) 63 | { 64 | $this->buf_ .= $buf; 65 | } 66 | 67 | public function read($len) 68 | { 69 | $bufLength = TStringFuncFactory::create()->strlen($this->buf_); 70 | 71 | if ($bufLength === 0) { 72 | throw new TTransportException( 73 | 'TMemoryBuffer: Could not read ' . 74 | $len . ' bytes from buffer.', 75 | TTransportException::UNKNOWN 76 | ); 77 | } 78 | 79 | if ($bufLength <= $len) { 80 | $ret = $this->buf_; 81 | $this->buf_ = ''; 82 | 83 | return $ret; 84 | } 85 | 86 | $ret = TStringFuncFactory::create()->substr($this->buf_, 0, $len); 87 | $this->buf_ = TStringFuncFactory::create()->substr($this->buf_, $len); 88 | 89 | return $ret; 90 | } 91 | 92 | public function getBuffer() 93 | { 94 | return $this->buf_; 95 | } 96 | 97 | public function available() 98 | { 99 | return TStringFuncFactory::create()->strlen($this->buf_); 100 | } 101 | 102 | public function putBack($data) 103 | { 104 | $this->buf_ = $data . $this->buf_; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/Protocol/TMultiplexedProtocol.php: -------------------------------------------------------------------------------- 1 | TMultiplexedProtocol is a protocol-independent concrete decorator 29 | * that allows a Thrift client to communicate with a multiplexing Thrift server, 30 | * by prepending the service name to the function name during function calls. 31 | * 32 | * @package Thrift\Protocol 33 | */ 34 | class TMultiplexedProtocol extends TProtocolDecorator 35 | { 36 | /** 37 | * Separator between service name and function name. 38 | * Should be the same as used at multiplexed Thrift server. 39 | * 40 | * @var string 41 | */ 42 | const SEPARATOR = ":"; 43 | 44 | /** 45 | * The name of service. 46 | * 47 | * @var string 48 | */ 49 | private $serviceName_; 50 | 51 | /** 52 | * Constructor of TMultiplexedProtocol class. 53 | * 54 | * Wrap the specified protocol, allowing it to be used to communicate with a 55 | * multiplexing server. The $serviceName is required as it is 56 | * prepended to the message header so that the multiplexing server can broker 57 | * the function call to the proper service. 58 | * 59 | * @param TProtocol $protocol 60 | * @param string $serviceName The name of service. 61 | */ 62 | public function __construct(TProtocol $protocol, $serviceName) 63 | { 64 | parent::__construct($protocol); 65 | $this->serviceName_ = $serviceName; 66 | } 67 | 68 | /** 69 | * Writes the message header. 70 | * Prepends the service name to the function name, separated by TMultiplexedProtocol::SEPARATOR. 71 | * 72 | * @param string $name Function name. 73 | * @param int $type Message type. 74 | * @param int $seqid The sequence id of this message. 75 | */ 76 | public function writeMessageBegin($name, $type, $seqid) 77 | { 78 | if ($type == TMessageType::CALL || $type == TMessageType::ONEWAY) { 79 | $nameWithService = $this->serviceName_ . self::SEPARATOR . $name; 80 | parent::writeMessageBegin($nameWithService, $type, $seqid); 81 | } else { 82 | parent::writeMessageBegin($name, $type, $seqid); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/Server/TServerSocket.php: -------------------------------------------------------------------------------- 1 | host_ = $host; 72 | $this->port_ = $port; 73 | } 74 | 75 | /** 76 | * Sets the accept timeout 77 | * 78 | * @param int $acceptTimeout 79 | * @return void 80 | */ 81 | public function setAcceptTimeout($acceptTimeout) 82 | { 83 | $this->acceptTimeout_ = $acceptTimeout; 84 | } 85 | 86 | /** 87 | * Opens a new socket server handle 88 | * 89 | * @return void 90 | */ 91 | public function listen() 92 | { 93 | $this->listener_ = stream_socket_server('tcp://' . $this->host_ . ':' . $this->port_); 94 | } 95 | 96 | /** 97 | * Closes the socket server handle 98 | * 99 | * @return void 100 | */ 101 | public function close() 102 | { 103 | @fclose($this->listener_); 104 | $this->listener_ = null; 105 | } 106 | 107 | /** 108 | * Implementation of accept. If not client is accepted in the given time 109 | * 110 | * @return TSocket 111 | */ 112 | protected function acceptImpl() 113 | { 114 | $handle = @stream_socket_accept($this->listener_, $this->acceptTimeout_ / 1000.0); 115 | if (!$handle) { 116 | return null; 117 | } 118 | 119 | $socket = new TSocket(); 120 | $socket->setHandle($handle); 121 | 122 | return $socket; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/Serializer/TBinarySerializer.php: -------------------------------------------------------------------------------- 1 | getName(), 49 | TMessageType::REPLY, 50 | $object, 51 | 0, 52 | $protocol->isStrictWrite() 53 | ); 54 | 55 | $protocol->readMessageBegin($unused_name, $unused_type, $unused_seqid); 56 | } else { 57 | $object->write($protocol); 58 | } 59 | $protocol->getTransport()->flush(); 60 | 61 | return $transport->getBuffer(); 62 | } 63 | 64 | public static function deserialize($string_object, $class_name, $buffer_size = 8192) 65 | { 66 | $transport = new TMemoryBuffer(); 67 | $protocol = new TBinaryProtocolAccelerated($transport); 68 | if (function_exists('thrift_protocol_read_binary')) { 69 | // NOTE (t.heintz) TBinaryProtocolAccelerated internally wraps our TMemoryBuffer in a 70 | // TBufferedTransport, so we have to retrieve it again or risk losing data when writing 71 | // less than 512 bytes to the transport (see the comment there as well). 72 | // @see THRIFT-1579 73 | $protocol->writeMessageBegin('', TMessageType::REPLY, 0); 74 | $protocolTransport = $protocol->getTransport(); 75 | $protocolTransport->write($string_object); 76 | $protocolTransport->flush(); 77 | 78 | return thrift_protocol_read_binary($protocol, $class_name, $protocol->isStrictRead(), $buffer_size); 79 | } else { 80 | $transport->write($string_object); 81 | $object = new $class_name(); 82 | $object->read($protocol); 83 | 84 | return $object; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/Server/TForkingServer.php: -------------------------------------------------------------------------------- 1 | transport_->listen(); 40 | 41 | while (!$this->stop_) { 42 | try { 43 | $transport = $this->transport_->accept(); 44 | 45 | if ($transport != null) { 46 | $pid = pcntl_fork(); 47 | 48 | if ($pid > 0) { 49 | $this->handleParent($transport, $pid); 50 | } elseif ($pid === 0) { 51 | $this->handleChild($transport); 52 | } else { 53 | throw new TException('Failed to fork'); 54 | } 55 | } 56 | } catch (TTransportException $e) { 57 | } 58 | 59 | $this->collectChildren(); 60 | } 61 | } 62 | 63 | /** 64 | * Code run by the parent 65 | * 66 | * @param TTransport $transport 67 | * @param int $pid 68 | * @return void 69 | */ 70 | private function handleParent(TTransport $transport, $pid) 71 | { 72 | $this->children_[$pid] = $transport; 73 | } 74 | 75 | /** 76 | * Code run by the child. 77 | * 78 | * @param TTransport $transport 79 | * @return void 80 | */ 81 | private function handleChild(TTransport $transport) 82 | { 83 | try { 84 | $inputTransport = $this->inputTransportFactory_->getTransport($transport); 85 | $outputTransport = $this->outputTransportFactory_->getTransport($transport); 86 | $inputProtocol = $this->inputProtocolFactory_->getProtocol($inputTransport); 87 | $outputProtocol = $this->outputProtocolFactory_->getProtocol($outputTransport); 88 | while ($this->processor_->process($inputProtocol, $outputProtocol)) { 89 | } 90 | @$transport->close(); 91 | } catch (TTransportException $e) { 92 | } 93 | 94 | exit(0); 95 | } 96 | 97 | /** 98 | * Collects any children we may have 99 | * 100 | * @return void 101 | */ 102 | private function collectChildren() 103 | { 104 | foreach ($this->children_ as $pid => $transport) { 105 | if (pcntl_waitpid($pid, $status, WNOHANG) > 0) { 106 | unset($this->children_[$pid]); 107 | if ($transport) { 108 | @$transport->close(); 109 | } 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * Stops the server running. Kills the transport 116 | * and then stops the main serving loop 117 | * 118 | * @return void 119 | */ 120 | public function stop() 121 | { 122 | $this->transport_->close(); 123 | $this->stop_ = true; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/Transport/TPhpStream.php: -------------------------------------------------------------------------------- 1 | read_ = $mode & self::MODE_R; 50 | $this->write_ = $mode & self::MODE_W; 51 | } 52 | 53 | public function open() 54 | { 55 | if ($this->read_) { 56 | $this->inStream_ = @fopen(self::inStreamName(), 'r'); 57 | if (!is_resource($this->inStream_)) { 58 | throw new TException('TPhpStream: Could not open php://input'); 59 | } 60 | } 61 | if ($this->write_) { 62 | $this->outStream_ = @fopen('php://output', 'w'); 63 | if (!is_resource($this->outStream_)) { 64 | throw new TException('TPhpStream: Could not open php://output'); 65 | } 66 | } 67 | } 68 | 69 | public function close() 70 | { 71 | if ($this->read_) { 72 | @fclose($this->inStream_); 73 | $this->inStream_ = null; 74 | } 75 | if ($this->write_) { 76 | @fclose($this->outStream_); 77 | $this->outStream_ = null; 78 | } 79 | } 80 | 81 | public function isOpen() 82 | { 83 | return 84 | (!$this->read_ || is_resource($this->inStream_)) && 85 | (!$this->write_ || is_resource($this->outStream_)); 86 | } 87 | 88 | public function read($len) 89 | { 90 | $data = @fread($this->inStream_, $len); 91 | if ($data === false || $data === '') { 92 | throw new TException('TPhpStream: Could not read ' . $len . ' bytes'); 93 | } 94 | 95 | return $data; 96 | } 97 | 98 | public function write($buf) 99 | { 100 | while (TStringFuncFactory::create()->strlen($buf) > 0) { 101 | $got = @fwrite($this->outStream_, $buf); 102 | if ($got === 0 || $got === false) { 103 | throw new TException( 104 | 'TPhpStream: Could not write ' . TStringFuncFactory::create()->strlen($buf) . ' bytes' 105 | ); 106 | } 107 | $buf = TStringFuncFactory::create()->substr($buf, $got); 108 | } 109 | } 110 | 111 | public function flush() 112 | { 113 | @fflush($this->outStream_); 114 | } 115 | 116 | private static function inStreamName() 117 | { 118 | if (php_sapi_name() == 'cli') { 119 | return 'php://stdin'; 120 | } 121 | 122 | return 'php://input'; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/Transport/TSSLSocket.php: -------------------------------------------------------------------------------- 1 | host_ = $this->getSSLHost($host); 59 | $this->port_ = $port; 60 | $this->context_ = $context; 61 | $this->debugHandler_ = $debugHandler ? $debugHandler : 'error_log'; 62 | } 63 | 64 | /** 65 | * Creates a host name with SSL transport protocol 66 | * if no transport protocol already specified in 67 | * the host name. 68 | * 69 | * @param string $host Host to listen on 70 | * @return string $host Host name with transport protocol 71 | */ 72 | private function getSSLHost($host) 73 | { 74 | $transport_protocol_loc = strpos($host, "://"); 75 | if ($transport_protocol_loc === false) { 76 | $host = 'ssl://' . $host; 77 | } 78 | return $host; 79 | } 80 | 81 | /** 82 | * Connects the socket. 83 | */ 84 | public function open() 85 | { 86 | if ($this->isOpen()) { 87 | throw new TTransportException('Socket already connected', TTransportException::ALREADY_OPEN); 88 | } 89 | 90 | if (empty($this->host_)) { 91 | throw new TTransportException('Cannot open null host', TTransportException::NOT_OPEN); 92 | } 93 | 94 | if ($this->port_ <= 0) { 95 | throw new TTransportException('Cannot open without port', TTransportException::NOT_OPEN); 96 | } 97 | 98 | $this->handle_ = @stream_socket_client( 99 | $this->host_ . ':' . $this->port_, 100 | $errno, 101 | $errstr, 102 | $this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000), 103 | STREAM_CLIENT_CONNECT, 104 | $this->context_ 105 | ); 106 | 107 | // Connect failed? 108 | if ($this->handle_ === false) { 109 | $error = 'TSocket: Could not connect to ' . 110 | $this->host_ . ':' . $this->port_ . ' (' . $errstr . ' [' . $errno . '])'; 111 | if ($this->debug_) { 112 | call_user_func($this->debugHandler_, $error); 113 | } 114 | throw new TException($error); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/JsonSerialize/JsonSerializeTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Requires PHP 5.4 or newer!'); 37 | } 38 | /** @var \Composer\Autoload\ClassLoader $loader */ 39 | $loader = require __DIR__ . '/../../../../vendor/autoload.php'; 40 | $loader->addPsr4('', __DIR__ . '/../packages/phpjs'); 41 | } 42 | 43 | public function testEmptyStruct() 44 | { 45 | $empty = new \ThriftTest\EmptyStruct(array('non_existing_key' => 'bar')); 46 | $this->assertEquals(new stdClass(), json_decode(json_encode($empty))); 47 | } 48 | 49 | public function testStringsAndInts() 50 | { 51 | $input = array( 52 | 'string_thing' => 'foo', 53 | 'i64_thing' => 1234567890, 54 | ); 55 | $xtruct = new \ThriftTest\Xtruct($input); 56 | 57 | // Xtruct's 'i32_thing' and 'byte_thing' fields should not be present here! 58 | $expected = new stdClass(); 59 | $expected->string_thing = $input['string_thing']; 60 | $expected->i64_thing = $input['i64_thing']; 61 | $this->assertEquals($expected, json_decode(json_encode($xtruct))); 62 | } 63 | 64 | public function testNestedStructs() 65 | { 66 | $xtruct2 = new \ThriftTest\Xtruct2(array( 67 | 'byte_thing' => 42, 68 | 'struct_thing' => new \ThriftTest\Xtruct(array( 69 | 'i32_thing' => 123456, 70 | )), 71 | )); 72 | 73 | $expected = new stdClass(); 74 | $expected->byte_thing = $xtruct2->byte_thing; 75 | $expected->struct_thing = new stdClass(); 76 | $expected->struct_thing->i32_thing = $xtruct2->struct_thing->i32_thing; 77 | $this->assertEquals($expected, json_decode(json_encode($xtruct2))); 78 | } 79 | 80 | public function testInsanity() 81 | { 82 | $xinput = array('string_thing' => 'foo'); 83 | $xtruct = new \ThriftTest\Xtruct($xinput); 84 | $insanity = new \ThriftTest\Insanity(array( 85 | 'xtructs' => array($xtruct, $xtruct, $xtruct) 86 | )); 87 | $expected = new stdClass(); 88 | $expected->xtructs = array((object)$xinput, (object)$xinput, (object)$xinput); 89 | $this->assertEquals($expected, json_decode(json_encode($insanity))); 90 | } 91 | 92 | public function testNestedLists() 93 | { 94 | $bonk = new \ThriftTest\Bonk(array('message' => 'foo')); 95 | $nested = new \ThriftTest\NestedListsBonk(array('bonk' => array(array(array($bonk))))); 96 | $expected = new stdClass(); 97 | $expected->bonk = array(array(array((object)array('message' => 'foo')))); 98 | $this->assertEquals($expected, json_decode(json_encode($nested))); 99 | } 100 | 101 | public function testMaps() 102 | { 103 | $intmap = new \ThriftTest\ThriftTest_testMap_args(['thing' => [0 => 'zero']]); 104 | $emptymap = new \ThriftTest\ThriftTest_testMap_args([]); 105 | $this->assertEquals('{"thing":{"0":"zero"}}', json_encode($intmap)); 106 | $this->assertEquals('{}', json_encode($emptymap)); 107 | } 108 | 109 | public function testScalarTypes() 110 | { 111 | $b = new \ThriftTest\Bools(['im_true' => '1', 'im_false' => '0']); 112 | $this->assertEquals('{"im_true":true,"im_false":false}', json_encode($b)); 113 | $s = new \ThriftTest\StructA(['s' => 42]); 114 | $this->assertEquals('{"s":"42"}', json_encode($s)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/TMultiplexedProcessor.php: -------------------------------------------------------------------------------- 1 | TMultiplexedProcessor is a Processor allowing 32 | * a single TServer to provide multiple services. 33 | * 34 | *

To do so, you instantiate the processor and then register additional 35 | * processors with it, as shown in the following example:

36 | * 37 | *
38 | * $processor = new TMultiplexedProcessor(); 39 | * 40 | * processor->registerProcessor( 41 | * "Calculator", 42 | * new \tutorial\CalculatorProcessor(new CalculatorHandler())); 43 | * 44 | * processor->registerProcessor( 45 | * "WeatherReport", 46 | * new \tutorial\WeatherReportProcessor(new WeatherReportHandler())); 47 | * 48 | * $processor->process($protocol, $protocol); 49 | *
50 | */ 51 | 52 | class TMultiplexedProcessor 53 | { 54 | private $serviceProcessorMap_; 55 | 56 | /** 57 | * 'Register' a service with this TMultiplexedProcessor. This 58 | * allows us to broker requests to individual services by using the service 59 | * name to select them at request time. 60 | * 61 | * @param serviceName Name of a service, has to be identical to the name 62 | * declared in the Thrift IDL, e.g. "WeatherReport". 63 | * @param processor Implementation of a service, usually referred to 64 | * as "handlers", e.g. WeatherReportHandler implementing WeatherReport.Iface. 65 | */ 66 | public function registerProcessor($serviceName, $processor) 67 | { 68 | $this->serviceProcessorMap_[$serviceName] = $processor; 69 | } 70 | 71 | /** 72 | * This implementation of process performs the following steps: 73 | * 74 | *
    75 | *
  1. Read the beginning of the message.
  2. 76 | *
  3. Extract the service name from the message.
  4. 77 | *
  5. Using the service name to locate the appropriate processor.
  6. 78 | *
  7. Dispatch to the processor, with a decorated instance of TProtocol 79 | * that allows readMessageBegin() to return the original Message.
  8. 80 | *
81 | * 82 | * @throws TException If the message type is not CALL or ONEWAY, if 83 | * the service name was not found in the message, or if the service 84 | * name was not found in the service map. 85 | */ 86 | public function process(TProtocol $input, TProtocol $output) 87 | { 88 | /* 89 | Use the actual underlying protocol (e.g. TBinaryProtocol) to read the 90 | message header. This pulls the message "off the wire", which we'll 91 | deal with at the end of this method. 92 | */ 93 | $input->readMessageBegin($fname, $mtype, $rseqid); 94 | 95 | if ($mtype !== TMessageType::CALL && $mtype != TMessageType::ONEWAY) { 96 | throw new TException("This should not have happened!?"); 97 | } 98 | 99 | // Extract the service name and the new Message name. 100 | if (strpos($fname, TMultiplexedProtocol::SEPARATOR) === false) { 101 | throw new TException("Service name not found in message name: {$fname}. Did you " . 102 | "forget to use a TMultiplexProtocol in your client?"); 103 | } 104 | list($serviceName, $messageName) = explode(':', $fname, 2); 105 | if (!array_key_exists($serviceName, $this->serviceProcessorMap_)) { 106 | throw new TException("Service name not found: {$serviceName}. Did you forget " . 107 | "to call registerProcessor()?"); 108 | } 109 | 110 | // Dispatch processing to the stored processor 111 | $processor = $this->serviceProcessorMap_[$serviceName]; 112 | 113 | return $processor->process( 114 | new StoredMessageProtocol($input, $messageName, $mtype, $rseqid), 115 | $output 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | 20 | 21 | if WITH_TESTS 22 | SUBDIRS = test 23 | endif 24 | 25 | if WITH_PHP_EXTENSION 26 | %.so: 27 | cd src/ext/thrift_protocol/ && $(MAKE) 28 | 29 | phpconfdir=$(PHP_CONFIG_PREFIX) 30 | phpconf_DATA=thrift_protocol.ini 31 | 32 | phpmoduledir = `php-config --extension-dir` 33 | phpmodule_SCRIPTS = src/ext/thrift_protocol/modules/thrift_protocol.so 34 | 35 | distclean-local: 36 | if [ -f src/ext/thrift_protocol/Makefile ]; then cd src/ext/thrift_protocol/ && $(MAKE) distclean; fi 37 | cd $(phpmodule_SCRIPTS) && $(PHPIZE) --clean 38 | 39 | endif 40 | 41 | phpdir = $(PHP_PREFIX)/ 42 | php_DATA = \ 43 | lib/TMultiplexedProcessor.php 44 | 45 | phpbasedir = $(phpdir)/Base 46 | phpbase_DATA = \ 47 | lib/Base/TBase.php 48 | 49 | phpclassloaderdir = $(phpdir)/ClassLoader 50 | phpclassloader_DATA = \ 51 | lib/ClassLoader/ThriftClassLoader.php 52 | 53 | phpexceptiondir = $(phpdir)/Exception 54 | phpexception_DATA = \ 55 | lib/Exception/TApplicationException.php \ 56 | lib/Exception/TException.php \ 57 | lib/Exception/TProtocolException.php \ 58 | lib/Exception/TTransportException.php 59 | 60 | phpfactorydir = $(phpdir)/Factory 61 | phpfactory_DATA = \ 62 | lib/Factory/TBinaryProtocolFactory.php \ 63 | lib/Factory/TCompactProtocolFactory.php \ 64 | lib/Factory/TJSONProtocolFactory.php \ 65 | lib/Factory/TProtocolFactory.php \ 66 | lib/Factory/TStringFuncFactory.php \ 67 | lib/Factory/TTransportFactory.php 68 | 69 | phpprotocoldir = $(phpdir)/Protocol 70 | phpprotocol_DATA = \ 71 | lib/Protocol/TBinaryProtocolAccelerated.php \ 72 | lib/Protocol/TBinaryProtocol.php \ 73 | lib/Protocol/TCompactProtocol.php \ 74 | lib/Protocol/TJSONProtocol.php \ 75 | lib/Protocol/TMultiplexedProtocol.php \ 76 | lib/Protocol/TProtocol.php \ 77 | lib/Protocol/TProtocolDecorator.php \ 78 | lib/Protocol/TSimpleJSONProtocol.php 79 | 80 | phpprotocoljsondir = $(phpprotocoldir)/JSON 81 | phpprotocoljson_DATA = \ 82 | lib/Protocol/JSON/BaseContext.php \ 83 | lib/Protocol/JSON/ListContext.php \ 84 | lib/Protocol/JSON/LookaheadReader.php \ 85 | lib/Protocol/JSON/PairContext.php 86 | 87 | phpprotocolsimplejsondir = $(phpprotocoldir)/SimpleJSON 88 | phpprotocolsimplejson_DATA = \ 89 | lib/Protocol/SimpleJSON/CollectionMapKeyException.php \ 90 | lib/Protocol/SimpleJSON/Context.php \ 91 | lib/Protocol/SimpleJSON/ListContext.php \ 92 | lib/Protocol/SimpleJSON/MapContext.php \ 93 | lib/Protocol/SimpleJSON/StructContext.php 94 | 95 | phpserializerdir = $(phpdir)/Serializer 96 | phpserializer_DATA = \ 97 | lib/Serializer/TBinarySerializer.php 98 | 99 | phpserverdir = $(phpdir)/Server 100 | phpserver_DATA = \ 101 | lib/Server/TServerSocket.php \ 102 | lib/Server/TForkingServer.php \ 103 | lib/Server/TServer.php \ 104 | lib/Server/TServerTransport.php \ 105 | lib/Server/TSimpleServer.php 106 | 107 | phpstringfuncdir = $(phpdir)/StringFunc 108 | phpstringfunc_DATA = \ 109 | lib/StringFunc/Mbstring.php \ 110 | lib/StringFunc/Core.php \ 111 | lib/StringFunc/TStringFunc.php 112 | 113 | phptransportdir = $(phpdir)/Transport 114 | phptransport_DATA = \ 115 | lib/Transport/TBufferedTransport.php \ 116 | lib/Transport/TCurlClient.php \ 117 | lib/Transport/TFramedTransport.php \ 118 | lib/Transport/THttpClient.php \ 119 | lib/Transport/TMemoryBuffer.php \ 120 | lib/Transport/TNullTransport.php \ 121 | lib/Transport/TPhpStream.php \ 122 | lib/Transport/TSocket.php \ 123 | lib/Transport/TSocketPool.php \ 124 | lib/Transport/TTransport.php 125 | 126 | phptypedir = $(phpdir)/Type 127 | phptype_DATA = \ 128 | lib/Type/TMessageType.php \ 129 | lib/Type/TType.php \ 130 | lib/Type/TConstant.php 131 | 132 | clean-local: 133 | if [ -f src/ext/thrift_protocol/Makefile ]; then cd src/ext/thrift_protocol/ && $(MAKE) clean; fi 134 | 135 | 136 | EXTRA_DIST = \ 137 | lib \ 138 | src/autoload.php \ 139 | src/ext/thrift_protocol/config.m4 \ 140 | src/ext/thrift_protocol/config.w32 \ 141 | src/ext/thrift_protocol/php_thrift_protocol.cpp \ 142 | src/ext/thrift_protocol/php_thrift_protocol.h \ 143 | src/Thrift.php \ 144 | src/TStringUtils.php \ 145 | coding_standards.md \ 146 | thrift_protocol.ini \ 147 | README.apache.md \ 148 | README.md \ 149 | test/Fixtures.php \ 150 | test/TestValidators.thrift \ 151 | test/JsonSerialize/JsonSerializeTest.php \ 152 | test/Protocol/BinarySerializerTest.php \ 153 | test/Protocol/TJSONProtocolFixtures.php \ 154 | test/Protocol/TJSONProtocolTest.php \ 155 | test/Protocol/TSimpleJSONProtocolFixtures.php \ 156 | test/Protocol/TSimpleJSONProtocolTest.php \ 157 | test/Validator/BaseValidatorTest.php \ 158 | test/Validator/ValidatorTest.php \ 159 | test/Validator/ValidatorTestOop.php 160 | 161 | 162 | MAINTAINERCLEANFILES = \ 163 | Makefile.in 164 | 165 | -------------------------------------------------------------------------------- /test/Validator/BaseValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertNoReadValidator('ThriftTest\EmptyStruct'); 33 | $this->assertNoWriteValidator('ThriftTest\EmptyStruct'); 34 | } 35 | 36 | public function testBonkValidator() 37 | { 38 | $this->assertNoReadValidator('ThriftTest\Bonk'); 39 | $this->assertHasWriteValidator('ThriftTest\Bonk'); 40 | } 41 | 42 | public function testStructAValidator() 43 | { 44 | $this->assertHasReadValidator('ThriftTest\StructA'); 45 | $this->assertHasWriteValidator('ThriftTest\StructA'); 46 | } 47 | 48 | public function testUnionOfStringsValidator() 49 | { 50 | $this->assertNoWriteValidator('TestValidators\UnionOfStrings'); 51 | } 52 | 53 | public function testServiceResultValidator() 54 | { 55 | $this->assertNoReadValidator('TestValidators\TestService_test_result'); 56 | $this->assertNoWriteValidator('TestValidators\TestService_test_result'); 57 | } 58 | 59 | public function testReadEmpty() 60 | { 61 | $bonk = new \ThriftTest\Bonk(); 62 | $transport = new TMemoryBuffer("\000"); 63 | $protocol = new TBinaryProtocol($transport); 64 | $bonk->read($protocol); 65 | } 66 | 67 | public function testWriteEmpty() 68 | { 69 | $bonk = new \ThriftTest\Bonk(); 70 | $transport = new TMemoryBuffer(); 71 | $protocol = new TBinaryProtocol($transport); 72 | try { 73 | $bonk->write($protocol); 74 | $this->fail('Bonk was able to write an empty object'); 75 | } catch (TProtocolException $e) { 76 | } 77 | } 78 | 79 | public function testWriteWithMissingRequired() 80 | { 81 | // Check that we are not able to write StructA with a missing required field 82 | $structa = new \ThriftTest\StructA(); 83 | $transport = new TMemoryBuffer(); 84 | $protocol = new TBinaryProtocol($transport); 85 | 86 | try { 87 | $structa->write($protocol); 88 | $this->fail('StructA was able to write an empty object'); 89 | } catch (TProtocolException $e) { 90 | } 91 | } 92 | 93 | public function testReadStructA() 94 | { 95 | $transport = new TMemoryBuffer(base64_decode('CwABAAAAA2FiYwA=')); 96 | $protocol = new TBinaryProtocol($transport); 97 | $structa = new \ThriftTest\StructA(); 98 | $structa->read($protocol); 99 | $this->assertEquals("abc", $structa->s); 100 | } 101 | 102 | public function testWriteStructA() 103 | { 104 | $transport = new TMemoryBuffer(); 105 | $protocol = new TBinaryProtocol($transport); 106 | $structa = new \ThriftTest\StructA(); 107 | $structa->s = "abc"; 108 | $structa->write($protocol); 109 | $writeResult = base64_encode($transport->getBuffer()); 110 | $this->assertEquals('CwABAAAAA2FiYwA=', $writeResult); 111 | } 112 | 113 | protected static function assertHasReadValidator($class) 114 | { 115 | if (!static::hasReadValidator($class)) { 116 | static::fail($class . ' class should have a read validator'); 117 | } 118 | } 119 | 120 | protected static function assertNoReadValidator($class) 121 | { 122 | if (static::hasReadValidator($class)) { 123 | static::fail($class . ' class should not have a write validator'); 124 | } 125 | } 126 | 127 | protected static function assertHasWriteValidator($class) 128 | { 129 | if (!static::hasWriteValidator($class)) { 130 | static::fail($class . ' class should have a write validator'); 131 | } 132 | } 133 | 134 | protected static function assertNoWriteValidator($class) 135 | { 136 | if (static::hasWriteValidator($class)) { 137 | static::fail($class . ' class should not have a write validator'); 138 | } 139 | } 140 | 141 | private static function hasReadValidator($class) 142 | { 143 | $rc = new \ReflectionClass($class); 144 | 145 | return $rc->hasMethod('_validateForRead'); 146 | } 147 | 148 | private static function hasWriteValidator($class) 149 | { 150 | $rc = new \ReflectionClass($class); 151 | 152 | return $rc->hasMethod('_validateForWrite'); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lib/Transport/TFramedTransport.php: -------------------------------------------------------------------------------- 1 | transport_ = $transport; 78 | $this->read_ = $read; 79 | $this->write_ = $write; 80 | } 81 | 82 | public function isOpen() 83 | { 84 | return $this->transport_->isOpen(); 85 | } 86 | 87 | public function open() 88 | { 89 | $this->transport_->open(); 90 | } 91 | 92 | public function close() 93 | { 94 | $this->transport_->close(); 95 | } 96 | 97 | /** 98 | * Reads from the buffer. When more data is required reads another entire 99 | * chunk and serves future reads out of that. 100 | * 101 | * @param int $len How much data 102 | */ 103 | public function read($len) 104 | { 105 | if (!$this->read_) { 106 | return $this->transport_->read($len); 107 | } 108 | 109 | if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) { 110 | $this->readFrame(); 111 | } 112 | 113 | // Just return full buff 114 | if ($len >= TStringFuncFactory::create()->strlen($this->rBuf_)) { 115 | $out = $this->rBuf_; 116 | $this->rBuf_ = null; 117 | 118 | return $out; 119 | } 120 | 121 | // Return TStringFuncFactory::create()->substr 122 | $out = TStringFuncFactory::create()->substr($this->rBuf_, 0, $len); 123 | $this->rBuf_ = TStringFuncFactory::create()->substr($this->rBuf_, $len); 124 | 125 | return $out; 126 | } 127 | 128 | /** 129 | * Put previously read data back into the buffer 130 | * 131 | * @param string $data data to return 132 | */ 133 | public function putBack($data) 134 | { 135 | if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) { 136 | $this->rBuf_ = $data; 137 | } else { 138 | $this->rBuf_ = ($data . $this->rBuf_); 139 | } 140 | } 141 | 142 | /** 143 | * Reads a chunk of data into the internal read buffer. 144 | */ 145 | private function readFrame() 146 | { 147 | $buf = $this->transport_->readAll(4); 148 | $val = unpack('N', $buf); 149 | $sz = $val[1]; 150 | 151 | $this->rBuf_ = $this->transport_->readAll($sz); 152 | } 153 | 154 | /** 155 | * Writes some data to the pending output buffer. 156 | * 157 | * @param string $buf The data 158 | * @param int $len Limit of bytes to write 159 | */ 160 | public function write($buf, $len = null) 161 | { 162 | if (!$this->write_) { 163 | return $this->transport_->write($buf, $len); 164 | } 165 | 166 | if ($len !== null && $len < TStringFuncFactory::create()->strlen($buf)) { 167 | $buf = TStringFuncFactory::create()->substr($buf, 0, $len); 168 | } 169 | $this->wBuf_ .= $buf; 170 | } 171 | 172 | /** 173 | * Writes the output buffer to the stream in the format of a 4-byte length 174 | * followed by the actual data. 175 | */ 176 | public function flush() 177 | { 178 | if (!$this->write_ || TStringFuncFactory::create()->strlen($this->wBuf_) == 0) { 179 | return $this->transport_->flush(); 180 | } 181 | 182 | $out = pack('N', TStringFuncFactory::create()->strlen($this->wBuf_)); 183 | $out .= $this->wBuf_; 184 | 185 | // Note that we clear the internal wBuf_ prior to the underlying write 186 | // to ensure we're in a sane state (i.e. internal buffer cleaned) 187 | // if the underlying write throws up an exception 188 | $this->wBuf_ = ''; 189 | $this->transport_->write($out); 190 | $this->transport_->flush(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lib/Transport/TBufferedTransport.php: -------------------------------------------------------------------------------- 1 | transport_ = $transport; 78 | $this->rBufSize_ = $rBufSize; 79 | $this->wBufSize_ = $wBufSize; 80 | } 81 | 82 | public function isOpen() 83 | { 84 | return $this->transport_->isOpen(); 85 | } 86 | 87 | /** 88 | * @inheritdoc 89 | * 90 | * @throws TTransportException 91 | */ 92 | public function open() 93 | { 94 | $this->transport_->open(); 95 | } 96 | 97 | public function close() 98 | { 99 | $this->transport_->close(); 100 | } 101 | 102 | public function putBack($data) 103 | { 104 | if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) { 105 | $this->rBuf_ = $data; 106 | } else { 107 | $this->rBuf_ = ($data . $this->rBuf_); 108 | } 109 | } 110 | 111 | /** 112 | * The reason that we customize readAll here is that the majority of PHP 113 | * streams are already internally buffered by PHP. The socket stream, for 114 | * example, buffers internally and blocks if you call read with $len greater 115 | * than the amount of data available, unlike recv() in C. 116 | * 117 | * Therefore, use the readAll method of the wrapped transport inside 118 | * the buffered readAll. 119 | * 120 | * @throws TTransportException 121 | */ 122 | public function readAll($len) 123 | { 124 | $have = TStringFuncFactory::create()->strlen($this->rBuf_); 125 | if ($have == 0) { 126 | $data = $this->transport_->readAll($len); 127 | } elseif ($have < $len) { 128 | $data = $this->rBuf_; 129 | $this->rBuf_ = ''; 130 | $data .= $this->transport_->readAll($len - $have); 131 | } elseif ($have == $len) { 132 | $data = $this->rBuf_; 133 | $this->rBuf_ = ''; 134 | } elseif ($have > $len) { 135 | $data = TStringFuncFactory::create()->substr($this->rBuf_, 0, $len); 136 | $this->rBuf_ = TStringFuncFactory::create()->substr($this->rBuf_, $len); 137 | } 138 | 139 | return $data; 140 | } 141 | 142 | /** 143 | * @inheritdoc 144 | * 145 | * @param int $len 146 | * @return string 147 | * @throws TTransportException 148 | */ 149 | public function read($len) 150 | { 151 | if (TStringFuncFactory::create()->strlen($this->rBuf_) === 0) { 152 | $this->rBuf_ = $this->transport_->read($this->rBufSize_); 153 | } 154 | 155 | if (TStringFuncFactory::create()->strlen($this->rBuf_) <= $len) { 156 | $ret = $this->rBuf_; 157 | $this->rBuf_ = ''; 158 | 159 | return $ret; 160 | } 161 | 162 | $ret = TStringFuncFactory::create()->substr($this->rBuf_, 0, $len); 163 | $this->rBuf_ = TStringFuncFactory::create()->substr($this->rBuf_, $len); 164 | 165 | return $ret; 166 | } 167 | 168 | /** 169 | * @inheritdoc 170 | * 171 | * @param string $buf 172 | * @throws TTransportException 173 | */ 174 | public function write($buf) 175 | { 176 | $this->wBuf_ .= $buf; 177 | if (TStringFuncFactory::create()->strlen($this->wBuf_) >= $this->wBufSize_) { 178 | $out = $this->wBuf_; 179 | 180 | // Note that we clear the internal wBuf_ prior to the underlying write 181 | // to ensure we're in a sane state (i.e. internal buffer cleaned) 182 | // if the underlying write throws up an exception 183 | $this->wBuf_ = ''; 184 | $this->transport_->write($out); 185 | } 186 | } 187 | 188 | /** 189 | * @inheritdoc 190 | * 191 | * @throws TTransportException 192 | */ 193 | public function flush() 194 | { 195 | if (TStringFuncFactory::create()->strlen($this->wBuf_) > 0) { 196 | $out = $this->wBuf_; 197 | 198 | // Note that we clear the internal wBuf_ prior to the underlying write 199 | // to ensure we're in a sane state (i.e. internal buffer cleaned) 200 | // if the underlying write throws up an exception 201 | $this->wBuf_ = ''; 202 | $this->transport_->write($out); 203 | } 204 | $this->transport_->flush(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/ClassLoader/ThriftClassLoader.php: -------------------------------------------------------------------------------- 1 | apcu = $apc; 62 | $this->apcu_prefix = $apcu_prefix; 63 | } 64 | 65 | /** 66 | * Registers a namespace. 67 | * 68 | * @param string $namespace The namespace 69 | * @param array|string $paths The location(s) of the namespace 70 | */ 71 | public function registerNamespace($namespace, $paths) 72 | { 73 | $this->namespaces[$namespace] = (array)$paths; 74 | } 75 | 76 | /** 77 | * Registers a Thrift definition namespace. 78 | * 79 | * @param string $namespace The definition namespace 80 | * @param array|string $paths The location(s) of the definition namespace 81 | */ 82 | public function registerDefinition($namespace, $paths) 83 | { 84 | $this->definitions[$namespace] = (array)$paths; 85 | } 86 | 87 | /** 88 | * Registers this instance as an autoloader. 89 | * 90 | * @param Boolean $prepend Whether to prepend the autoloader or not 91 | */ 92 | public function register($prepend = false) 93 | { 94 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 95 | } 96 | 97 | /** 98 | * Loads the given class, definition or interface. 99 | * 100 | * @param string $class The name of the class 101 | */ 102 | public function loadClass($class) 103 | { 104 | if ((true === $this->apcu && ($file = $this->findFileInApcu($class))) or 105 | ($file = $this->findFile($class)) 106 | ) { 107 | require_once $file; 108 | } 109 | } 110 | 111 | /** 112 | * Loads the given class or interface in APCu. 113 | * @param string $class The name of the class 114 | * @return string 115 | */ 116 | protected function findFileInApcu($class) 117 | { 118 | if (false === $file = apcu_fetch($this->apcu_prefix . $class)) { 119 | apcu_store($this->apcu_prefix . $class, $file = $this->findFile($class)); 120 | } 121 | 122 | return $file; 123 | } 124 | 125 | /** 126 | * Find class in namespaces or definitions directories 127 | * @param string $class 128 | * @return string 129 | */ 130 | public function findFile($class) 131 | { 132 | // Remove first backslash 133 | if ('\\' == $class[0]) { 134 | $class = substr($class, 1); 135 | } 136 | 137 | if (false !== $pos = strrpos($class, '\\')) { 138 | // Namespaced class name 139 | $namespace = substr($class, 0, $pos); 140 | 141 | // Iterate in normal namespaces 142 | foreach ($this->namespaces as $ns => $dirs) { 143 | //Don't interfere with other autoloaders 144 | if (0 !== strpos($namespace, $ns)) { 145 | continue; 146 | } 147 | 148 | foreach ($dirs as $dir) { 149 | $className = substr($class, $pos + 1); 150 | 151 | $file = $dir . DIRECTORY_SEPARATOR . 152 | str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . 153 | DIRECTORY_SEPARATOR . 154 | $className . '.php'; 155 | 156 | if (file_exists($file)) { 157 | return $file; 158 | } 159 | } 160 | } 161 | 162 | // Iterate in Thrift namespaces 163 | 164 | // Remove first part of namespace 165 | $m = explode('\\', $class); 166 | 167 | // Ignore wrong call 168 | if (count($m) <= 1) { 169 | return; 170 | } 171 | 172 | $class = array_pop($m); 173 | $namespace = implode('\\', $m); 174 | 175 | foreach ($this->definitions as $ns => $dirs) { 176 | //Don't interfere with other autoloaders 177 | if (0 !== strpos($namespace, $ns)) { 178 | continue; 179 | } 180 | 181 | foreach ($dirs as $dir) { 182 | /** 183 | * Available in service: Interface, Client, Processor, Rest 184 | * And every service methods (_.+) 185 | */ 186 | if (0 === preg_match('#(.+)(if|client|processor|rest)$#i', $class, $n) and 187 | 0 === preg_match('#(.+)_[a-z0-9]+_(args|result)$#i', $class, $n) 188 | ) { 189 | $className = 'Types'; 190 | } else { 191 | $className = $n[1]; 192 | } 193 | 194 | $file = $dir . DIRECTORY_SEPARATOR . 195 | str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . 196 | DIRECTORY_SEPARATOR . 197 | $className . '.php'; 198 | 199 | if (file_exists($file)) { 200 | return $file; 201 | } 202 | } 203 | } 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/Transport/THttpClient.php: -------------------------------------------------------------------------------- 1 | strlen($uri) > 0) && ($uri[0] != '/')) { 110 | $uri = '/' . $uri; 111 | } 112 | $this->scheme_ = $scheme; 113 | $this->host_ = $host; 114 | $this->port_ = $port; 115 | $this->uri_ = $uri; 116 | $this->buf_ = ''; 117 | $this->handle_ = null; 118 | $this->timeout_ = null; 119 | $this->headers_ = array(); 120 | $this->context_ = $context; 121 | } 122 | 123 | /** 124 | * Set read timeout 125 | * 126 | * @param float $timeout 127 | */ 128 | public function setTimeoutSecs($timeout) 129 | { 130 | $this->timeout_ = $timeout; 131 | } 132 | 133 | /** 134 | * Whether this transport is open. 135 | * 136 | * @return boolean true if open 137 | */ 138 | public function isOpen() 139 | { 140 | return true; 141 | } 142 | 143 | /** 144 | * Open the transport for reading/writing 145 | * 146 | * @throws TTransportException if cannot open 147 | */ 148 | public function open() 149 | { 150 | } 151 | 152 | /** 153 | * Close the transport. 154 | */ 155 | public function close() 156 | { 157 | if ($this->handle_) { 158 | @fclose($this->handle_); 159 | $this->handle_ = null; 160 | } 161 | } 162 | 163 | /** 164 | * Read some data into the array. 165 | * 166 | * @param int $len How much to read 167 | * @return string The data that has been read 168 | * @throws TTransportException if cannot read any more data 169 | */ 170 | public function read($len) 171 | { 172 | $data = @fread($this->handle_, $len); 173 | if ($data === false || $data === '') { 174 | $md = stream_get_meta_data($this->handle_); 175 | if ($md['timed_out']) { 176 | throw new TTransportException( 177 | 'THttpClient: timed out reading ' . $len . ' bytes from ' . 178 | $this->host_ . ':' . $this->port_ . $this->uri_, 179 | TTransportException::TIMED_OUT 180 | ); 181 | } else { 182 | throw new TTransportException( 183 | 'THttpClient: Could not read ' . $len . ' bytes from ' . 184 | $this->host_ . ':' . $this->port_ . $this->uri_, 185 | TTransportException::UNKNOWN 186 | ); 187 | } 188 | } 189 | 190 | return $data; 191 | } 192 | 193 | /** 194 | * Writes some data into the pending buffer 195 | * 196 | * @param string $buf The data to write 197 | * @throws TTransportException if writing fails 198 | */ 199 | public function write($buf) 200 | { 201 | $this->buf_ .= $buf; 202 | } 203 | 204 | /** 205 | * Opens and sends the actual request over the HTTP connection 206 | * 207 | * @throws TTransportException if a writing error occurs 208 | */ 209 | public function flush() 210 | { 211 | // God, PHP really has some esoteric ways of doing simple things. 212 | $host = $this->host_ . ($this->port_ != 80 ? ':' . $this->port_ : ''); 213 | 214 | $headers = array(); 215 | $defaultHeaders = array('Host' => $host, 216 | 'Accept' => 'application/x-thrift', 217 | 'User-Agent' => 'PHP/THttpClient', 218 | 'Content-Type' => 'application/x-thrift', 219 | 'Content-Length' => TStringFuncFactory::create()->strlen($this->buf_)); 220 | foreach (array_merge($defaultHeaders, $this->headers_) as $key => $value) { 221 | $headers[] = "$key: $value"; 222 | } 223 | 224 | $options = $this->context_; 225 | 226 | $baseHttpOptions = isset($options["http"]) ? $options["http"] : array(); 227 | 228 | $httpOptions = $baseHttpOptions + array('method' => 'POST', 229 | 'header' => implode("\r\n", $headers), 230 | 'max_redirects' => 1, 231 | 'content' => $this->buf_); 232 | if ($this->timeout_ > 0) { 233 | $httpOptions['timeout'] = $this->timeout_; 234 | } 235 | $this->buf_ = ''; 236 | 237 | $options["http"] = $httpOptions; 238 | $contextid = stream_context_create($options); 239 | $this->handle_ = @fopen( 240 | $this->scheme_ . '://' . $host . $this->uri_, 241 | 'r', 242 | false, 243 | $contextid 244 | ); 245 | 246 | // Connect failed? 247 | if ($this->handle_ === false) { 248 | $this->handle_ = null; 249 | $error = 'THttpClient: Could not connect to ' . $host . $this->uri_; 250 | throw new TTransportException($error, TTransportException::NOT_OPEN); 251 | } 252 | } 253 | 254 | public function addHeaders($headers) 255 | { 256 | $this->headers_ = array_merge($this->headers_, $headers); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /test/Fixtures.php: -------------------------------------------------------------------------------- 1 | <><"; 46 | 47 | self::$testArgs['testString3'] = 48 | "string that ends in double-backslash \\\\"; 49 | 50 | self::$testArgs['testUnicodeStringWithNonBMP'] = 51 | "สวัสดี/𝒯"; 52 | 53 | self::$testArgs['testDouble'] = 3.1415926535898; 54 | 55 | // TODO: add testBinary() call 56 | 57 | self::$testArgs['testByte'] = 0x01; 58 | 59 | self::$testArgs['testI32'] = pow(2, 30); 60 | 61 | if (PHP_INT_SIZE == 8) { 62 | self::$testArgs['testI64'] = pow(2, 60); 63 | } else { 64 | self::$testArgs['testI64'] = "1152921504606847000"; 65 | } 66 | 67 | self::$testArgs['testStruct'] = 68 | new Xtruct( 69 | array( 70 | 'string_thing' => 'worked', 71 | 'byte_thing' => 0x01, 72 | 'i32_thing' => pow(2, 30), 73 | 'i64_thing' => self::$testArgs['testI64'] 74 | ) 75 | ); 76 | 77 | self::$testArgs['testNestNested'] = 78 | new Xtruct( 79 | array( 80 | 'string_thing' => 'worked', 81 | 'byte_thing' => 0x01, 82 | 'i32_thing' => pow(2, 30), 83 | 'i64_thing' => self::$testArgs['testI64'] 84 | ) 85 | ); 86 | 87 | self::$testArgs['testNest'] = 88 | new Xtruct2( 89 | array( 90 | 'byte_thing' => 0x01, 91 | 'struct_thing' => self::$testArgs['testNestNested'], 92 | 'i32_thing' => pow(2, 15) 93 | ) 94 | ); 95 | 96 | self::$testArgs['testMap'] = 97 | array( 98 | 7 => 77, 99 | 8 => 88, 100 | 9 => 99 101 | ); 102 | 103 | self::$testArgs['testStringMap'] = 104 | array( 105 | "a" => "123", 106 | "a b" => "with spaces ", 107 | "same" => "same", 108 | "0" => "numeric key", 109 | "longValue" => self::$testArgs['testString1'], 110 | self::$testArgs['testString1'] => "long key" 111 | ); 112 | 113 | self::$testArgs['testSet'] = array(1 => true, 5 => true, 6 => true); 114 | 115 | self::$testArgs['testList'] = array(1, 2, 3); 116 | 117 | self::$testArgs['testEnum'] = Numberz::ONE; 118 | 119 | self::$testArgs['testTypedef'] = 69; 120 | 121 | self::$testArgs['testMapMapExpectedResult'] = 122 | array( 123 | 4 => array( 124 | 1 => 1, 125 | 2 => 2, 126 | 3 => 3, 127 | 4 => 4, 128 | ), 129 | -4 => array( 130 | -4 => -4, 131 | -3 => -3, 132 | -2 => -2, 133 | -1 => -1 134 | ) 135 | ); 136 | 137 | // testInsanity ... takes a few steps to set up! 138 | 139 | $xtruct1 = 140 | new Xtruct( 141 | array( 142 | 'string_thing' => 'Goodbye4', 143 | 'byte_thing' => 4, 144 | 'i32_thing' => 4, 145 | 'i64_thing' => 4 146 | ) 147 | ); 148 | 149 | $xtruct2 = 150 | new Xtruct( 151 | array( 152 | 'string_thing' => 'Hello2', 153 | 'byte_thing' => 2, 154 | 'i32_thing' => 2, 155 | 'i64_thing' => 2 156 | ) 157 | ); 158 | 159 | $userMap = 160 | array( 161 | Numberz::FIVE => 5, 162 | Numberz::EIGHT => 8 163 | ); 164 | 165 | $insanity2 = 166 | new Insanity( 167 | array( 168 | 'userMap' => $userMap, 169 | 'xtructs' => array($xtruct1, $xtruct2) 170 | ) 171 | ); 172 | 173 | $insanity3 = $insanity2; 174 | 175 | $insanity6 = 176 | new Insanity( 177 | array( 178 | 'userMap' => null, 179 | 'xtructs' => null 180 | ) 181 | ); 182 | 183 | self::$testArgs['testInsanityExpectedResult'] = 184 | array( 185 | "1" => array( 186 | Numberz::TWO => $insanity2, 187 | Numberz::THREE => $insanity3 188 | ), 189 | "2" => array( 190 | Numberz::SIX => $insanity6 191 | ) 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/Protocol/TProtocolDecorator.php: -------------------------------------------------------------------------------- 1 | TProtocolDecorator forwards all requests to an enclosed 29 | * TProtocol instance, providing a way to author concise 30 | * concrete decorator subclasses. While it has no abstract methods, it 31 | * is marked abstract as a reminder that by itself, it does not modify 32 | * the behaviour of the enclosed TProtocol. 33 | * 34 | * @package Thrift\Protocol 35 | */ 36 | abstract class TProtocolDecorator extends TProtocol 37 | { 38 | /** 39 | * Instance of protocol, to which all operations will be forwarded. 40 | * 41 | * @var TProtocol 42 | */ 43 | private $concreteProtocol_; 44 | 45 | /** 46 | * Constructor of TProtocolDecorator class. 47 | * Encloses the specified protocol. 48 | * 49 | * @param TProtocol $protocol All operations will be forward to this instance. Must be non-null. 50 | */ 51 | protected function __construct(TProtocol $protocol) 52 | { 53 | parent::__construct($protocol->getTransport()); 54 | $this->concreteProtocol_ = $protocol; 55 | } 56 | 57 | /** 58 | * Writes the message header. 59 | * 60 | * @param string $name Function name 61 | * @param int $type message type TMessageType::CALL or TMessageType::REPLY 62 | * @param int $seqid The sequence id of this message 63 | */ 64 | public function writeMessageBegin($name, $type, $seqid) 65 | { 66 | return $this->concreteProtocol_->writeMessageBegin($name, $type, $seqid); 67 | } 68 | 69 | /** 70 | * Closes the message. 71 | */ 72 | public function writeMessageEnd() 73 | { 74 | return $this->concreteProtocol_->writeMessageEnd(); 75 | } 76 | 77 | /** 78 | * Writes a struct header. 79 | * 80 | * @param string $name Struct name 81 | * 82 | * @throws TException on write error 83 | * @return int How many bytes written 84 | */ 85 | public function writeStructBegin($name) 86 | { 87 | return $this->concreteProtocol_->writeStructBegin($name); 88 | } 89 | 90 | /** 91 | * Close a struct. 92 | * 93 | * @throws TException on write error 94 | * @return int How many bytes written 95 | */ 96 | public function writeStructEnd() 97 | { 98 | return $this->concreteProtocol_->writeStructEnd(); 99 | } 100 | 101 | public function writeFieldBegin($fieldName, $fieldType, $fieldId) 102 | { 103 | return $this->concreteProtocol_->writeFieldBegin($fieldName, $fieldType, $fieldId); 104 | } 105 | 106 | public function writeFieldEnd() 107 | { 108 | return $this->concreteProtocol_->writeFieldEnd(); 109 | } 110 | 111 | public function writeFieldStop() 112 | { 113 | return $this->concreteProtocol_->writeFieldStop(); 114 | } 115 | 116 | public function writeMapBegin($keyType, $valType, $size) 117 | { 118 | return $this->concreteProtocol_->writeMapBegin($keyType, $valType, $size); 119 | } 120 | 121 | public function writeMapEnd() 122 | { 123 | return $this->concreteProtocol_->writeMapEnd(); 124 | } 125 | 126 | public function writeListBegin($elemType, $size) 127 | { 128 | return $this->concreteProtocol_->writeListBegin($elemType, $size); 129 | } 130 | 131 | public function writeListEnd() 132 | { 133 | return $this->concreteProtocol_->writeListEnd(); 134 | } 135 | 136 | public function writeSetBegin($elemType, $size) 137 | { 138 | return $this->concreteProtocol_->writeSetBegin($elemType, $size); 139 | } 140 | 141 | public function writeSetEnd() 142 | { 143 | return $this->concreteProtocol_->writeSetEnd(); 144 | } 145 | 146 | public function writeBool($bool) 147 | { 148 | return $this->concreteProtocol_->writeBool($bool); 149 | } 150 | 151 | public function writeByte($byte) 152 | { 153 | return $this->concreteProtocol_->writeByte($byte); 154 | } 155 | 156 | public function writeI16($i16) 157 | { 158 | return $this->concreteProtocol_->writeI16($i16); 159 | } 160 | 161 | public function writeI32($i32) 162 | { 163 | return $this->concreteProtocol_->writeI32($i32); 164 | } 165 | 166 | public function writeI64($i64) 167 | { 168 | return $this->concreteProtocol_->writeI64($i64); 169 | } 170 | 171 | public function writeDouble($dub) 172 | { 173 | return $this->concreteProtocol_->writeDouble($dub); 174 | } 175 | 176 | public function writeString($str) 177 | { 178 | return $this->concreteProtocol_->writeString($str); 179 | } 180 | 181 | /** 182 | * Reads the message header 183 | * 184 | * @param string $name Function name 185 | * @param int $type message type TMessageType::CALL or TMessageType::REPLY 186 | * @param int $seqid The sequence id of this message 187 | */ 188 | public function readMessageBegin(&$name, &$type, &$seqid) 189 | { 190 | return $this->concreteProtocol_->readMessageBegin($name, $type, $seqid); 191 | } 192 | 193 | /** 194 | * Read the close of message 195 | */ 196 | public function readMessageEnd() 197 | { 198 | return $this->concreteProtocol_->readMessageEnd(); 199 | } 200 | 201 | public function readStructBegin(&$name) 202 | { 203 | return $this->concreteProtocol_->readStructBegin($name); 204 | } 205 | 206 | public function readStructEnd() 207 | { 208 | return $this->concreteProtocol_->readStructEnd(); 209 | } 210 | 211 | public function readFieldBegin(&$name, &$fieldType, &$fieldId) 212 | { 213 | return $this->concreteProtocol_->readFieldBegin($name, $fieldType, $fieldId); 214 | } 215 | 216 | public function readFieldEnd() 217 | { 218 | return $this->concreteProtocol_->readFieldEnd(); 219 | } 220 | 221 | public function readMapBegin(&$keyType, &$valType, &$size) 222 | { 223 | $this->concreteProtocol_->readMapBegin($keyType, $valType, $size); 224 | } 225 | 226 | public function readMapEnd() 227 | { 228 | return $this->concreteProtocol_->readMapEnd(); 229 | } 230 | 231 | public function readListBegin(&$elemType, &$size) 232 | { 233 | $this->concreteProtocol_->readListBegin($elemType, $size); 234 | } 235 | 236 | public function readListEnd() 237 | { 238 | return $this->concreteProtocol_->readListEnd(); 239 | } 240 | 241 | public function readSetBegin(&$elemType, &$size) 242 | { 243 | return $this->concreteProtocol_->readSetBegin($elemType, $size); 244 | } 245 | 246 | public function readSetEnd() 247 | { 248 | return $this->concreteProtocol_->readSetEnd(); 249 | } 250 | 251 | public function readBool(&$bool) 252 | { 253 | return $this->concreteProtocol_->readBool($bool); 254 | } 255 | 256 | public function readByte(&$byte) 257 | { 258 | return $this->concreteProtocol_->readByte($byte); 259 | } 260 | 261 | public function readI16(&$i16) 262 | { 263 | return $this->concreteProtocol_->readI16($i16); 264 | } 265 | 266 | public function readI32(&$i32) 267 | { 268 | return $this->concreteProtocol_->readI32($i32); 269 | } 270 | 271 | public function readI64(&$i64) 272 | { 273 | return $this->concreteProtocol_->readI64($i64); 274 | } 275 | 276 | public function readDouble(&$dub) 277 | { 278 | return $this->concreteProtocol_->readDouble($dub); 279 | } 280 | 281 | public function readString(&$str) 282 | { 283 | return $this->concreteProtocol_->readString($str); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /test/Protocol/TSimpleJSONProtocolTest.php: -------------------------------------------------------------------------------- 1 | addPsr4('', __DIR__ . '/../packages/php'); 53 | 54 | Fixtures::populateTestArgs(); 55 | TSimpleJSONProtocolFixtures::populateTestArgsSimpleJSON(); 56 | } 57 | 58 | public function setUp() 59 | { 60 | $this->transport = new TMemoryBuffer(); 61 | $this->protocol = new TSimpleJSONProtocol($this->transport); 62 | $this->transport->open(); 63 | } 64 | 65 | /** 66 | * WRITE TESTS 67 | */ 68 | public function testVoidWrite() 69 | { 70 | $args = new \ThriftTest\ThriftTest_testVoid_args(); 71 | $args->write($this->protocol); 72 | 73 | $actual = $this->transport->read(Fixtures::$bufsize); 74 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testVoid']; 75 | 76 | $this->assertEquals($expected, $actual); 77 | } 78 | 79 | public function testString1Write() 80 | { 81 | $args = new \ThriftTest\ThriftTest_testString_args(); 82 | $args->thing = Fixtures::$testArgs['testString1']; 83 | $args->write($this->protocol); 84 | 85 | $actual = $this->transport->read(Fixtures::$bufsize); 86 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testString1']; 87 | 88 | $this->assertEquals($expected, $actual); 89 | } 90 | 91 | public function testString2Write() 92 | { 93 | $args = new \ThriftTest\ThriftTest_testString_args(); 94 | $args->thing = Fixtures::$testArgs['testString2']; 95 | $args->write($this->protocol); 96 | 97 | $actual = $this->transport->read(Fixtures::$bufsize); 98 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testString2']; 99 | 100 | $this->assertEquals($expected, $actual); 101 | } 102 | 103 | public function testDoubleWrite() 104 | { 105 | $args = new \ThriftTest\ThriftTest_testDouble_args(); 106 | $args->thing = Fixtures::$testArgs['testDouble']; 107 | $args->write($this->protocol); 108 | 109 | $actual = $this->transport->read(Fixtures::$bufsize); 110 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testDouble']; 111 | 112 | $this->assertEquals($expected, $actual); 113 | } 114 | 115 | public function testByteWrite() 116 | { 117 | $args = new \ThriftTest\ThriftTest_testByte_args(); 118 | $args->thing = Fixtures::$testArgs['testByte']; 119 | $args->write($this->protocol); 120 | 121 | $actual = $this->transport->read(Fixtures::$bufsize); 122 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testByte']; 123 | 124 | $this->assertEquals($expected, $actual); 125 | } 126 | 127 | public function testI32Write() 128 | { 129 | $args = new \ThriftTest\ThriftTest_testI32_args(); 130 | $args->thing = Fixtures::$testArgs['testI32']; 131 | $args->write($this->protocol); 132 | 133 | $actual = $this->transport->read(Fixtures::$bufsize); 134 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testI32']; 135 | 136 | $this->assertEquals($expected, $actual); 137 | } 138 | 139 | public function testI64Write() 140 | { 141 | $args = new \ThriftTest\ThriftTest_testI64_args(); 142 | $args->thing = Fixtures::$testArgs['testI64']; 143 | $args->write($this->protocol); 144 | 145 | $actual = $this->transport->read(Fixtures::$bufsize); 146 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testI64']; 147 | 148 | $this->assertEquals($expected, $actual); 149 | } 150 | 151 | public function testStructWrite() 152 | { 153 | $args = new \ThriftTest\ThriftTest_testStruct_args(); 154 | $args->thing = Fixtures::$testArgs['testStruct']; 155 | 156 | $args->write($this->protocol); 157 | 158 | $actual = $this->transport->read(Fixtures::$bufsize); 159 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testStruct']; 160 | 161 | $this->assertEquals($expected, $actual); 162 | } 163 | 164 | public function testNestWrite() 165 | { 166 | $args = new \ThriftTest\ThriftTest_testNest_args(); 167 | $args->thing = Fixtures::$testArgs['testNest']; 168 | 169 | $args->write($this->protocol); 170 | 171 | $actual = $this->transport->read(Fixtures::$bufsize); 172 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testNest']; 173 | 174 | $this->assertEquals($expected, $actual); 175 | } 176 | 177 | public function testMapWrite() 178 | { 179 | $args = new \ThriftTest\ThriftTest_testMap_args(); 180 | $args->thing = Fixtures::$testArgs['testMap']; 181 | 182 | $args->write($this->protocol); 183 | 184 | $actual = $this->transport->read(Fixtures::$bufsize); 185 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testMap']; 186 | 187 | $this->assertEquals($expected, $actual); 188 | } 189 | 190 | public function testStringMapWrite() 191 | { 192 | $args = new \ThriftTest\ThriftTest_testStringMap_args(); 193 | $args->thing = Fixtures::$testArgs['testStringMap']; 194 | 195 | $args->write($this->protocol); 196 | 197 | $actual = $this->transport->read(Fixtures::$bufsize); 198 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testStringMap']; 199 | 200 | $this->assertEquals($expected, $actual); 201 | } 202 | 203 | public function testSetWrite() 204 | { 205 | $args = new \ThriftTest\ThriftTest_testSet_args(); 206 | $args->thing = Fixtures::$testArgs['testSet']; 207 | 208 | $args->write($this->protocol); 209 | 210 | $actual = $this->transport->read(Fixtures::$bufsize); 211 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testSet']; 212 | 213 | $this->assertEquals($expected, $actual); 214 | } 215 | 216 | public function testListWrite() 217 | { 218 | $args = new \ThriftTest\ThriftTest_testList_args(); 219 | $args->thing = Fixtures::$testArgs['testList']; 220 | 221 | $args->write($this->protocol); 222 | 223 | $actual = $this->transport->read(Fixtures::$bufsize); 224 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testList']; 225 | 226 | $this->assertEquals($expected, $actual); 227 | } 228 | 229 | public function testEnumWrite() 230 | { 231 | $args = new \ThriftTest\ThriftTest_testEnum_args(); 232 | $args->thing = Fixtures::$testArgs['testEnum']; 233 | 234 | $args->write($this->protocol); 235 | 236 | $actual = $this->transport->read(Fixtures::$bufsize); 237 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testEnum']; 238 | 239 | $this->assertEquals($expected, $actual); 240 | } 241 | 242 | public function testTypedefWrite() 243 | { 244 | $args = new \ThriftTest\ThriftTest_testTypedef_args(); 245 | $args->thing = Fixtures::$testArgs['testTypedef']; 246 | 247 | $args->write($this->protocol); 248 | 249 | $actual = $this->transport->read(Fixtures::$bufsize); 250 | $expected = TSimpleJSONProtocolFixtures::$testArgsJSON['testTypedef']; 251 | 252 | $this->assertEquals($expected, $actual); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /lib/Transport/TCurlClient.php: -------------------------------------------------------------------------------- 1 | strlen($uri) > 0) && ($uri[0] != '/')) { 110 | $uri = '/' . $uri; 111 | } 112 | $this->scheme_ = $scheme; 113 | $this->host_ = $host; 114 | $this->port_ = $port; 115 | $this->uri_ = $uri; 116 | $this->request_ = ''; 117 | $this->response_ = null; 118 | $this->timeout_ = null; 119 | $this->connectionTimeout_ = null; 120 | $this->headers_ = array(); 121 | } 122 | 123 | /** 124 | * Set read timeout 125 | * 126 | * @param float $timeout 127 | */ 128 | public function setTimeoutSecs($timeout) 129 | { 130 | $this->timeout_ = $timeout; 131 | } 132 | 133 | /** 134 | * Set connection timeout 135 | * 136 | * @param float $connectionTimeout 137 | */ 138 | public function setConnectionTimeoutSecs($connectionTimeout) 139 | { 140 | $this->connectionTimeout_ = $connectionTimeout; 141 | } 142 | 143 | /** 144 | * Whether this transport is open. 145 | * 146 | * @return boolean true if open 147 | */ 148 | public function isOpen() 149 | { 150 | return true; 151 | } 152 | 153 | /** 154 | * Open the transport for reading/writing 155 | * 156 | * @throws TTransportException if cannot open 157 | */ 158 | public function open() 159 | { 160 | } 161 | 162 | /** 163 | * Close the transport. 164 | */ 165 | public function close() 166 | { 167 | $this->request_ = ''; 168 | $this->response_ = null; 169 | } 170 | 171 | /** 172 | * Read some data into the array. 173 | * 174 | * @param int $len How much to read 175 | * @return string The data that has been read 176 | * @throws TTransportException if cannot read any more data 177 | */ 178 | public function read($len) 179 | { 180 | if ($len >= strlen($this->response_)) { 181 | return $this->response_; 182 | } else { 183 | $ret = substr($this->response_, 0, $len); 184 | $this->response_ = substr($this->response_, $len); 185 | 186 | return $ret; 187 | } 188 | } 189 | 190 | /** 191 | * Guarantees that the full amount of data is read. Since TCurlClient gets entire payload at 192 | * once, parent readAll cannot be used. 193 | * 194 | * @return string The data, of exact length 195 | * @throws TTransportException if cannot read data 196 | */ 197 | public function readAll($len) 198 | { 199 | $data = $this->read($len); 200 | 201 | if (TStringFuncFactory::create()->strlen($data) !== $len) { 202 | throw new TTransportException('TCurlClient could not read '.$len.' bytes'); 203 | } 204 | 205 | return $data; 206 | } 207 | 208 | /** 209 | * Writes some data into the pending buffer 210 | * 211 | * @param string $buf The data to write 212 | * @throws TTransportException if writing fails 213 | */ 214 | public function write($buf) 215 | { 216 | $this->request_ .= $buf; 217 | } 218 | 219 | /** 220 | * Opens and sends the actual request over the HTTP connection 221 | * 222 | * @throws TTransportException if a writing error occurs 223 | */ 224 | public function flush() 225 | { 226 | if (!self::$curlHandle) { 227 | register_shutdown_function(array('Thrift\\Transport\\TCurlClient', 'closeCurlHandle')); 228 | self::$curlHandle = curl_init(); 229 | curl_setopt(self::$curlHandle, CURLOPT_RETURNTRANSFER, true); 230 | curl_setopt(self::$curlHandle, CURLOPT_BINARYTRANSFER, true); 231 | curl_setopt(self::$curlHandle, CURLOPT_USERAGENT, 'PHP/TCurlClient'); 232 | curl_setopt(self::$curlHandle, CURLOPT_CUSTOMREQUEST, 'POST'); 233 | curl_setopt(self::$curlHandle, CURLOPT_FOLLOWLOCATION, true); 234 | curl_setopt(self::$curlHandle, CURLOPT_MAXREDIRS, 1); 235 | } 236 | // God, PHP really has some esoteric ways of doing simple things. 237 | $host = $this->host_ . ($this->port_ != 80 ? ':' . $this->port_ : ''); 238 | $fullUrl = $this->scheme_ . "://" . $host . $this->uri_; 239 | 240 | $headers = array(); 241 | $defaultHeaders = array('Accept' => 'application/x-thrift', 242 | 'Content-Type' => 'application/x-thrift', 243 | 'Content-Length' => TStringFuncFactory::create()->strlen($this->request_)); 244 | foreach (array_merge($defaultHeaders, $this->headers_) as $key => $value) { 245 | $headers[] = "$key: $value"; 246 | } 247 | 248 | curl_setopt(self::$curlHandle, CURLOPT_HTTPHEADER, $headers); 249 | 250 | if ($this->timeout_ > 0) { 251 | if ($this->timeout_ < 1.0) { 252 | // Timestamps smaller than 1 second are ignored when CURLOPT_TIMEOUT is used 253 | curl_setopt(self::$curlHandle, CURLOPT_TIMEOUT_MS, 1000 * $this->timeout_); 254 | } else { 255 | curl_setopt(self::$curlHandle, CURLOPT_TIMEOUT, $this->timeout_); 256 | } 257 | } 258 | if ($this->connectionTimeout_ > 0) { 259 | if ($this->connectionTimeout_ < 1.0) { 260 | // Timestamps smaller than 1 second are ignored when CURLOPT_CONNECTTIMEOUT is used 261 | curl_setopt(self::$curlHandle, CURLOPT_CONNECTTIMEOUT_MS, 1000 * $this->connectionTimeout_); 262 | } else { 263 | curl_setopt(self::$curlHandle, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout_); 264 | } 265 | } 266 | curl_setopt(self::$curlHandle, CURLOPT_POSTFIELDS, $this->request_); 267 | $this->request_ = ''; 268 | 269 | curl_setopt(self::$curlHandle, CURLOPT_URL, $fullUrl); 270 | $this->response_ = curl_exec(self::$curlHandle); 271 | $responseError = curl_error(self::$curlHandle); 272 | 273 | $code = curl_getinfo(self::$curlHandle, CURLINFO_HTTP_CODE); 274 | 275 | // Handle non 200 status code / connect failure 276 | if ($this->response_ === false || $code !== 200) { 277 | curl_close(self::$curlHandle); 278 | self::$curlHandle = null; 279 | $this->response_ = null; 280 | $error = 'TCurlClient: Could not connect to ' . $fullUrl; 281 | if ($responseError) { 282 | $error .= ', ' . $responseError; 283 | } 284 | if ($code) { 285 | $error .= ', HTTP status code: ' . $code; 286 | } 287 | throw new TTransportException($error, TTransportException::UNKNOWN); 288 | } 289 | } 290 | 291 | public static function closeCurlHandle() 292 | { 293 | try { 294 | if (self::$curlHandle) { 295 | curl_close(self::$curlHandle); 296 | self::$curlHandle = null; 297 | } 298 | } catch (\Exception $x) { 299 | error_log('There was an error closing the curl handle: ' . $x->getMessage()); 300 | } 301 | } 302 | 303 | public function addHeaders($headers) 304 | { 305 | $this->headers_ = array_merge($this->headers_, $headers); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /lib/Protocol/TSimpleJSONProtocol.php: -------------------------------------------------------------------------------- 1 | writeContextStack_[] = $this->writeContext_; 60 | $this->writeContext_ = $c; 61 | } 62 | 63 | /** 64 | * Pop the last write context off the stack 65 | */ 66 | protected function popWriteContext() 67 | { 68 | $this->writeContext_ = array_pop($this->writeContextStack_); 69 | } 70 | 71 | /** 72 | * Used to make sure that we are not encountering a map whose keys are containers 73 | */ 74 | protected function assertContextIsNotMapKey($invalidKeyType) 75 | { 76 | if ($this->writeContext_->isMapKey()) { 77 | throw new CollectionMapKeyException( 78 | "Cannot serialize a map with keys that are of type " . 79 | $invalidKeyType 80 | ); 81 | } 82 | } 83 | 84 | private function writeJSONString($b) 85 | { 86 | $this->writeContext_->write(); 87 | 88 | $this->trans_->write(json_encode((string)$b)); 89 | } 90 | 91 | private function writeJSONInteger($num) 92 | { 93 | $isMapKey = $this->writeContext_->isMapKey(); 94 | 95 | $this->writeContext_->write(); 96 | 97 | if ($isMapKey) { 98 | $this->trans_->write(self::QUOTE); 99 | } 100 | 101 | $this->trans_->write((int)$num); 102 | 103 | if ($isMapKey) { 104 | $this->trans_->write(self::QUOTE); 105 | } 106 | } 107 | 108 | private function writeJSONDouble($num) 109 | { 110 | $isMapKey = $this->writeContext_->isMapKey(); 111 | 112 | $this->writeContext_->write(); 113 | 114 | if ($isMapKey) { 115 | $this->trans_->write(self::QUOTE); 116 | } 117 | 118 | $this->trans_->write(json_encode((float)$num)); 119 | 120 | if ($isMapKey) { 121 | $this->trans_->write(self::QUOTE); 122 | } 123 | } 124 | 125 | /** 126 | * Constructor 127 | */ 128 | public function __construct($trans) 129 | { 130 | parent::__construct($trans); 131 | $this->writeContext_ = new Context(); 132 | } 133 | 134 | /** 135 | * Writes the message header 136 | * 137 | * @param string $name Function name 138 | * @param int $type message type TMessageType::CALL or TMessageType::REPLY 139 | * @param int $seqid The sequence id of this message 140 | */ 141 | public function writeMessageBegin($name, $type, $seqid) 142 | { 143 | $this->trans_->write(self::LBRACKET); 144 | $this->pushWriteContext(new ListContext($this)); 145 | $this->writeJSONString($name); 146 | $this->writeJSONInteger($type); 147 | $this->writeJSONInteger($seqid); 148 | } 149 | 150 | /** 151 | * Close the message 152 | */ 153 | public function writeMessageEnd() 154 | { 155 | $this->popWriteContext(); 156 | $this->trans_->write(self::RBRACKET); 157 | } 158 | 159 | /** 160 | * Writes a struct header. 161 | * 162 | * @param string $name Struct name 163 | */ 164 | public function writeStructBegin($name) 165 | { 166 | $this->writeContext_->write(); 167 | $this->trans_->write(self::LBRACE); 168 | $this->pushWriteContext(new StructContext($this)); 169 | } 170 | 171 | /** 172 | * Close a struct. 173 | */ 174 | public function writeStructEnd() 175 | { 176 | $this->popWriteContext(); 177 | $this->trans_->write(self::RBRACE); 178 | } 179 | 180 | public function writeFieldBegin($fieldName, $fieldType, $fieldId) 181 | { 182 | $this->writeJSONString($fieldName); 183 | } 184 | 185 | public function writeFieldEnd() 186 | { 187 | } 188 | 189 | public function writeFieldStop() 190 | { 191 | } 192 | 193 | public function writeMapBegin($keyType, $valType, $size) 194 | { 195 | $this->assertContextIsNotMapKey(self::NAME_MAP); 196 | $this->writeContext_->write(); 197 | $this->trans_->write(self::LBRACE); 198 | $this->pushWriteContext(new MapContext($this)); 199 | } 200 | 201 | public function writeMapEnd() 202 | { 203 | $this->popWriteContext(); 204 | $this->trans_->write(self::RBRACE); 205 | } 206 | 207 | public function writeListBegin($elemType, $size) 208 | { 209 | $this->assertContextIsNotMapKey(self::NAME_LIST); 210 | $this->writeContext_->write(); 211 | $this->trans_->write(self::LBRACKET); 212 | $this->pushWriteContext(new ListContext($this)); 213 | // No metadata! 214 | } 215 | 216 | public function writeListEnd() 217 | { 218 | $this->popWriteContext(); 219 | $this->trans_->write(self::RBRACKET); 220 | } 221 | 222 | public function writeSetBegin($elemType, $size) 223 | { 224 | $this->assertContextIsNotMapKey(self::NAME_SET); 225 | $this->writeContext_->write(); 226 | $this->trans_->write(self::LBRACKET); 227 | $this->pushWriteContext(new ListContext($this)); 228 | // No metadata! 229 | } 230 | 231 | public function writeSetEnd() 232 | { 233 | $this->popWriteContext(); 234 | $this->trans_->write(self::RBRACKET); 235 | } 236 | 237 | public function writeBool($bool) 238 | { 239 | $this->writeJSONInteger($bool ? 1 : 0); 240 | } 241 | 242 | public function writeByte($byte) 243 | { 244 | $this->writeJSONInteger($byte); 245 | } 246 | 247 | public function writeI16($i16) 248 | { 249 | $this->writeJSONInteger($i16); 250 | } 251 | 252 | public function writeI32($i32) 253 | { 254 | $this->writeJSONInteger($i32); 255 | } 256 | 257 | public function writeI64($i64) 258 | { 259 | $this->writeJSONInteger($i64); 260 | } 261 | 262 | public function writeDouble($dub) 263 | { 264 | $this->writeJSONDouble($dub); 265 | } 266 | 267 | public function writeString($str) 268 | { 269 | $this->writeJSONString($str); 270 | } 271 | 272 | /** 273 | * Reading methods. 274 | * 275 | * simplejson is not meant to be read back into thrift 276 | * - see http://wiki.apache.org/thrift/ThriftUsageJava 277 | * - use JSON instead 278 | */ 279 | 280 | public function readMessageBegin(&$name, &$type, &$seqid) 281 | { 282 | throw new TException("Not implemented"); 283 | } 284 | 285 | public function readMessageEnd() 286 | { 287 | throw new TException("Not implemented"); 288 | } 289 | 290 | public function readStructBegin(&$name) 291 | { 292 | throw new TException("Not implemented"); 293 | } 294 | 295 | public function readStructEnd() 296 | { 297 | throw new TException("Not implemented"); 298 | } 299 | 300 | public function readFieldBegin(&$name, &$fieldType, &$fieldId) 301 | { 302 | throw new TException("Not implemented"); 303 | } 304 | 305 | public function readFieldEnd() 306 | { 307 | throw new TException("Not implemented"); 308 | } 309 | 310 | public function readMapBegin(&$keyType, &$valType, &$size) 311 | { 312 | throw new TException("Not implemented"); 313 | } 314 | 315 | public function readMapEnd() 316 | { 317 | throw new TException("Not implemented"); 318 | } 319 | 320 | public function readListBegin(&$elemType, &$size) 321 | { 322 | throw new TException("Not implemented"); 323 | } 324 | 325 | public function readListEnd() 326 | { 327 | throw new TException("Not implemented"); 328 | } 329 | 330 | public function readSetBegin(&$elemType, &$size) 331 | { 332 | throw new TException("Not implemented"); 333 | } 334 | 335 | public function readSetEnd() 336 | { 337 | throw new TException("Not implemented"); 338 | } 339 | 340 | public function readBool(&$bool) 341 | { 342 | throw new TException("Not implemented"); 343 | } 344 | 345 | public function readByte(&$byte) 346 | { 347 | throw new TException("Not implemented"); 348 | } 349 | 350 | public function readI16(&$i16) 351 | { 352 | throw new TException("Not implemented"); 353 | } 354 | 355 | public function readI32(&$i32) 356 | { 357 | throw new TException("Not implemented"); 358 | } 359 | 360 | public function readI64(&$i64) 361 | { 362 | throw new TException("Not implemented"); 363 | } 364 | 365 | public function readDouble(&$dub) 366 | { 367 | throw new TException("Not implemented"); 368 | } 369 | 370 | public function readString(&$str) 371 | { 372 | throw new TException("Not implemented"); 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /lib/Transport/TSocketPool.php: -------------------------------------------------------------------------------- 1 | $val) { 114 | $ports[$key] = $port; 115 | } 116 | } 117 | 118 | foreach ($hosts as $key => $host) { 119 | $this->servers_ [] = array('host' => $host, 120 | 'port' => $ports[$key]); 121 | } 122 | } 123 | 124 | /** 125 | * Add a server to the pool 126 | * 127 | * This function does not prevent you from adding a duplicate server entry. 128 | * 129 | * @param string $host hostname or IP 130 | * @param int $port port 131 | */ 132 | public function addServer($host, $port) 133 | { 134 | $this->servers_[] = array('host' => $host, 'port' => $port); 135 | } 136 | 137 | /** 138 | * Sets how many time to keep retrying a host in the connect function. 139 | * 140 | * @param int $numRetries 141 | */ 142 | public function setNumRetries($numRetries) 143 | { 144 | $this->numRetries_ = $numRetries; 145 | } 146 | 147 | /** 148 | * Sets how long to wait until retrying a host if it was marked down 149 | * 150 | * @param int $numRetries 151 | */ 152 | public function setRetryInterval($retryInterval) 153 | { 154 | $this->retryInterval_ = $retryInterval; 155 | } 156 | 157 | /** 158 | * Sets how many time to keep retrying a host before marking it as down. 159 | * 160 | * @param int $numRetries 161 | */ 162 | public function setMaxConsecutiveFailures($maxConsecutiveFailures) 163 | { 164 | $this->maxConsecutiveFailures_ = $maxConsecutiveFailures; 165 | } 166 | 167 | /** 168 | * Turns randomization in connect order on or off. 169 | * 170 | * @param bool $randomize 171 | */ 172 | public function setRandomize($randomize) 173 | { 174 | $this->randomize_ = $randomize; 175 | } 176 | 177 | /** 178 | * Whether to always try the last server. 179 | * 180 | * @param bool $alwaysTryLast 181 | */ 182 | public function setAlwaysTryLast($alwaysTryLast) 183 | { 184 | $this->alwaysTryLast_ = $alwaysTryLast; 185 | } 186 | 187 | /** 188 | * Connects the socket by iterating through all the servers in the pool 189 | * and trying to find one that works. 190 | */ 191 | public function open() 192 | { 193 | // Check if we want order randomization 194 | if ($this->randomize_) { 195 | shuffle($this->servers_); 196 | } 197 | 198 | // Count servers to identify the "last" one 199 | $numServers = count($this->servers_); 200 | 201 | for ($i = 0; $i < $numServers; ++$i) { 202 | // This extracts the $host and $port variables 203 | extract($this->servers_[$i]); 204 | 205 | // Check APCu cache for a record of this server being down 206 | $failtimeKey = 'thrift_failtime:' . $host . ':' . $port . '~'; 207 | 208 | // Cache miss? Assume it's OK 209 | $lastFailtime = apcu_fetch($failtimeKey); 210 | if ($lastFailtime === false) { 211 | $lastFailtime = 0; 212 | } 213 | 214 | $retryIntervalPassed = false; 215 | 216 | // Cache hit...make sure enough the retry interval has elapsed 217 | if ($lastFailtime > 0) { 218 | $elapsed = time() - $lastFailtime; 219 | if ($elapsed > $this->retryInterval_) { 220 | $retryIntervalPassed = true; 221 | if ($this->debug_) { 222 | call_user_func( 223 | $this->debugHandler_, 224 | 'TSocketPool: retryInterval ' . 225 | '(' . $this->retryInterval_ . ') ' . 226 | 'has passed for host ' . $host . ':' . $port 227 | ); 228 | } 229 | } 230 | } 231 | 232 | // Only connect if not in the middle of a fail interval, OR if this 233 | // is the LAST server we are trying, just hammer away on it 234 | $isLastServer = false; 235 | if ($this->alwaysTryLast_) { 236 | $isLastServer = ($i == ($numServers - 1)); 237 | } 238 | 239 | if (($lastFailtime === 0) || 240 | ($isLastServer) || 241 | ($lastFailtime > 0 && $retryIntervalPassed)) { 242 | // Set underlying TSocket params to this one 243 | $this->host_ = $host; 244 | $this->port_ = $port; 245 | 246 | // Try up to numRetries_ connections per server 247 | for ($attempt = 0; $attempt < $this->numRetries_; $attempt++) { 248 | try { 249 | // Use the underlying TSocket open function 250 | parent::open(); 251 | 252 | // Only clear the failure counts if required to do so 253 | if ($lastFailtime > 0) { 254 | apcu_store($failtimeKey, 0); 255 | } 256 | 257 | // Successful connection, return now 258 | return; 259 | } catch (TException $tx) { 260 | // Connection failed 261 | } 262 | } 263 | 264 | // Mark failure of this host in the cache 265 | $consecfailsKey = 'thrift_consecfails:' . $host . ':' . $port . '~'; 266 | 267 | // Ignore cache misses 268 | $consecfails = apcu_fetch($consecfailsKey); 269 | if ($consecfails === false) { 270 | $consecfails = 0; 271 | } 272 | 273 | // Increment by one 274 | $consecfails++; 275 | 276 | // Log and cache this failure 277 | if ($consecfails >= $this->maxConsecutiveFailures_) { 278 | if ($this->debug_) { 279 | call_user_func( 280 | $this->debugHandler_, 281 | 'TSocketPool: marking ' . $host . ':' . $port . 282 | ' as down for ' . $this->retryInterval_ . ' secs ' . 283 | 'after ' . $consecfails . ' failed attempts.' 284 | ); 285 | } 286 | // Store the failure time 287 | apcu_store($failtimeKey, time()); 288 | 289 | // Clear the count of consecutive failures 290 | apcu_store($consecfailsKey, 0); 291 | } else { 292 | apcu_store($consecfailsKey, $consecfails); 293 | } 294 | } 295 | } 296 | 297 | // Oh no; we failed them all. The system is totally ill! 298 | $error = 'TSocketPool: All hosts in pool are down. '; 299 | $hosts = array(); 300 | foreach ($this->servers_ as $server) { 301 | $hosts [] = $server['host'] . ':' . $server['port']; 302 | } 303 | $hostlist = implode(',', $hosts); 304 | $error .= '(' . $hostlist . ')'; 305 | if ($this->debug_) { 306 | call_user_func($this->debugHandler_, $error); 307 | } 308 | throw new TException($error); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /lib/Transport/TSocket.php: -------------------------------------------------------------------------------- 1 | host_ = $host; 129 | $this->port_ = $port; 130 | $this->persist_ = $persist; 131 | $this->debugHandler_ = $debugHandler ? $debugHandler : 'error_log'; 132 | } 133 | 134 | /** 135 | * @param resource $handle 136 | * @return void 137 | */ 138 | public function setHandle($handle) 139 | { 140 | $this->handle_ = $handle; 141 | stream_set_blocking($this->handle_, false); 142 | } 143 | 144 | /** 145 | * Sets the send timeout. 146 | * 147 | * @param int $timeout Timeout in milliseconds. 148 | */ 149 | public function setSendTimeout($timeout) 150 | { 151 | $this->sendTimeoutSec_ = floor($timeout / 1000); 152 | $this->sendTimeoutUsec_ = 153 | ($timeout - ($this->sendTimeoutSec_ * 1000)) * 1000; 154 | } 155 | 156 | /** 157 | * Sets the receive timeout. 158 | * 159 | * @param int $timeout Timeout in milliseconds. 160 | */ 161 | public function setRecvTimeout($timeout) 162 | { 163 | $this->recvTimeoutSec_ = floor($timeout / 1000); 164 | $this->recvTimeoutUsec_ = 165 | ($timeout - ($this->recvTimeoutSec_ * 1000)) * 1000; 166 | } 167 | 168 | /** 169 | * Sets debugging output on or off 170 | * 171 | * @param bool $debug 172 | */ 173 | public function setDebug($debug) 174 | { 175 | $this->debug_ = $debug; 176 | } 177 | 178 | /** 179 | * Get the host that this socket is connected to 180 | * 181 | * @return string host 182 | */ 183 | public function getHost() 184 | { 185 | return $this->host_; 186 | } 187 | 188 | /** 189 | * Get the remote port that this socket is connected to 190 | * 191 | * @return int port 192 | */ 193 | public function getPort() 194 | { 195 | return $this->port_; 196 | } 197 | 198 | /** 199 | * Tests whether this is open 200 | * 201 | * @return bool true if the socket is open 202 | */ 203 | public function isOpen() 204 | { 205 | return is_resource($this->handle_); 206 | } 207 | 208 | /** 209 | * Connects the socket. 210 | */ 211 | public function open() 212 | { 213 | if ($this->isOpen()) { 214 | throw new TTransportException('Socket already connected', TTransportException::ALREADY_OPEN); 215 | } 216 | 217 | if (empty($this->host_)) { 218 | throw new TTransportException('Cannot open null host', TTransportException::NOT_OPEN); 219 | } 220 | 221 | if ($this->port_ <= 0) { 222 | throw new TTransportException('Cannot open without port', TTransportException::NOT_OPEN); 223 | } 224 | 225 | if ($this->persist_) { 226 | $this->handle_ = @pfsockopen( 227 | $this->host_, 228 | $this->port_, 229 | $errno, 230 | $errstr, 231 | $this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000) 232 | ); 233 | } else { 234 | $this->handle_ = @fsockopen( 235 | $this->host_, 236 | $this->port_, 237 | $errno, 238 | $errstr, 239 | $this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000) 240 | ); 241 | } 242 | 243 | // Connect failed? 244 | if ($this->handle_ === false) { 245 | $error = 'TSocket: Could not connect to ' . 246 | $this->host_ . ':' . $this->port_ . ' (' . $errstr . ' [' . $errno . '])'; 247 | if ($this->debug_) { 248 | call_user_func($this->debugHandler_, $error); 249 | } 250 | throw new TException($error); 251 | } 252 | 253 | if (function_exists('socket_import_stream') && function_exists('socket_set_option')) { 254 | // warnings silenced due to bug https://bugs.php.net/bug.php?id=70939 255 | $socket = @socket_import_stream($this->handle_); 256 | @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); 257 | } 258 | } 259 | 260 | /** 261 | * Closes the socket. 262 | */ 263 | public function close() 264 | { 265 | @fclose($this->handle_); 266 | $this->handle_ = null; 267 | } 268 | 269 | /** 270 | * Read from the socket at most $len bytes. 271 | * 272 | * This method will not wait for all the requested data, it will return as 273 | * soon as any data is received. 274 | * 275 | * @param int $len Maximum number of bytes to read. 276 | * @return string Binary data 277 | */ 278 | public function read($len) 279 | { 280 | $null = null; 281 | $read = array($this->handle_); 282 | $readable = @stream_select( 283 | $read, 284 | $null, 285 | $null, 286 | $this->recvTimeoutSec_, 287 | $this->recvTimeoutUsec_ 288 | ); 289 | 290 | if ($readable > 0) { 291 | $data = fread($this->handle_, $len); 292 | if ($data === false) { 293 | throw new TTransportException('TSocket: Could not read ' . $len . ' bytes from ' . 294 | $this->host_ . ':' . $this->port_); 295 | } elseif ($data == '' && feof($this->handle_)) { 296 | throw new TTransportException('TSocket read 0 bytes'); 297 | } 298 | 299 | return $data; 300 | } elseif ($readable === 0) { 301 | throw new TTransportException('TSocket: timed out reading ' . $len . ' bytes from ' . 302 | $this->host_ . ':' . $this->port_); 303 | } else { 304 | throw new TTransportException('TSocket: Could not read ' . $len . ' bytes from ' . 305 | $this->host_ . ':' . $this->port_); 306 | } 307 | } 308 | 309 | /** 310 | * Write to the socket. 311 | * 312 | * @param string $buf The data to write 313 | */ 314 | public function write($buf) 315 | { 316 | $null = null; 317 | $write = array($this->handle_); 318 | 319 | // keep writing until all the data has been written 320 | while (TStringFuncFactory::create()->strlen($buf) > 0) { 321 | // wait for stream to become available for writing 322 | $writable = @stream_select( 323 | $null, 324 | $write, 325 | $null, 326 | $this->sendTimeoutSec_, 327 | $this->sendTimeoutUsec_ 328 | ); 329 | if ($writable > 0) { 330 | // write buffer to stream 331 | $written = fwrite($this->handle_, $buf); 332 | $closed_socket = $written === 0 && feof($this->handle_); 333 | if ($written === -1 || $written === false || $closed_socket) { 334 | throw new TTransportException( 335 | 'TSocket: Could not write ' . TStringFuncFactory::create()->strlen($buf) . ' bytes ' . 336 | $this->host_ . ':' . $this->port_ 337 | ); 338 | } 339 | // determine how much of the buffer is left to write 340 | $buf = TStringFuncFactory::create()->substr($buf, $written); 341 | } elseif ($writable === 0) { 342 | throw new TTransportException( 343 | 'TSocket: timed out writing ' . TStringFuncFactory::create()->strlen($buf) . ' bytes from ' . 344 | $this->host_ . ':' . $this->port_ 345 | ); 346 | } else { 347 | throw new TTransportException( 348 | 'TSocket: Could not write ' . TStringFuncFactory::create()->strlen($buf) . ' bytes ' . 349 | $this->host_ . ':' . $this->port_ 350 | ); 351 | } 352 | } 353 | } 354 | 355 | /** 356 | * Flush output to the socket. 357 | * 358 | * Since read(), readAll() and write() operate on the sockets directly, 359 | * this is a no-op 360 | * 361 | * If you wish to have flushable buffering behaviour, wrap this TSocket 362 | * in a TBufferedTransport. 363 | */ 364 | public function flush() 365 | { 366 | // no-op 367 | } 368 | } 369 | --------------------------------------------------------------------------------