├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENCE ├── README.md ├── composer.json ├── composer.lock ├── doc ├── QAP1_protocol.txt ├── QAP_message.txt └── original_version_simple.php ├── example ├── config.php ├── error_handling.php ├── evaluator.php └── example1.php ├── phpunit.xml ├── rserve.R ├── src ├── ArrayWrapper.php ├── Connection.php ├── Evaluator.php ├── Exception.php ├── Parser.php ├── Parser │ ├── Debug.php │ ├── Exception.php │ ├── NativeArray.php │ └── REXP.php ├── Protocol.php ├── REXP.php ├── REXP │ ├── Complex.php │ ├── Dataframe.php │ ├── Double.php │ ├── Error.php │ ├── Expression.php │ ├── Factor.php │ ├── GenericVector.php │ ├── Integer.php │ ├── Language.php │ ├── Logical.php │ ├── RList.php │ ├── RNull.php │ ├── RString.php │ ├── Raw.php │ ├── Symbol.php │ ├── Unknown.php │ └── Vector.php ├── Serializer.php ├── Session.php ├── autoload.php └── lib │ └── helpers.php └── tests ├── .gitignore ├── BaseTest.php ├── ConnectionManager.php ├── Definition.php ├── LoginTest.php ├── ParserNativeTest.php ├── REXPTest.php ├── SessionTest.php ├── bootstrap.php └── config.php.sample /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .buildpath 3 | .settings 4 | vendor 5 | tests/BaseTest.php 6 | *.cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | language: php 7 | 8 | php: 9 | - 7 10 | 11 | before_install: 12 | - composer install 13 | - cp tests/config.php.sample tests/config.php 14 | - docker run -d -p 6311:6311 wnagele/rserve 15 | 16 | script: 17 | - composer test 18 | 19 | after_success: 20 | - docker ps|grep rserve|awk '{print "docker stop " $1}'|bash 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 2.1 3 | 4 | - Minimum php version 7.4 5 | - Fix php 8.0 compatibilites for NativeArray 6 | - Adapt code style for php 7.4, adding types 7 | 8 | # 2.0 9 | 10 | - All classes are declared under Sentiweb\Rserve namespace allowing PSR-4 autoloading 11 | - Parsers are now individualized into classes 12 | - A Parser instance can be directly used as second argument of evalString() to replace default parser (see example) 13 | 14 | # Before TDB -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | rserve-php - php interface to R 2 | Copyright (C) 2010 Clément Turbelin, Simon Urbanek 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 2.1 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rserve-php 2 | ========== 3 | 4 | php5 client for Rserve http://www.rforge.net/Rserve/ (a TCP/IP server for R statistical software) 5 | 6 | [![Build Status](https://travis-ci.org/shadiakiki1986/rserve-php.svg?branch=2.0-improvements)](https://travis-ci.org/shadiakiki1986/rserve-php) 7 | 8 | Changes from 1.0 version 9 | --- 10 | - All classes are declared under Sentiweb\Rserve namespace allowing PSR-4 autoloading 11 | - Parsers are now individualized into classes 12 | - A Parser instance can be directly used as second argument of evalString() to replace default parser (see example) 13 | 14 | Tests 15 | ----- 16 | 17 | You can run tests using phpunit 18 | 19 | Several tests need to have a running Rserve server (not handled by this library). 20 | To configure the test to use a server you have to configure the connection using environment vars or by creating a file in tests/config.php and defining constants (a sample file is available in tests/config.php.sample). 21 | Expected vars (in env or as constant in tests/config.php) 22 | - `RSERVE_HOST` : hostname or IP of the Rserve server (e.g. 'localhost' or 127.0.0.1 for local server). 23 | - `RSERVE_PORT` : port number, if value is '0' or 'unix', the HOST is expected to be a unix socket path. 24 | - `RSERVE_USER` : username if the Rserve server is expecting authentication, skip or leave empty if not 25 | - `RSERVE_PASS` : password if the Rserve server is expecting authentication, skip or leave empty if not 26 | 27 | `RSERVE_HOST` is required to be defined (either env or in config.php) to run the connection aware tests, if not these tests will be skipped. 28 | 29 | Credential-free tests 30 | * launch your credential-free Rserve instance 31 | * Can be done with `docker run -d -p 6311:6311 wnagele/rserve` 32 | 33 | * if installed with composer: `composer test` 34 | * otherwise, run with `phpunit` 35 | 36 | Login tests: 37 | This test suite require a credential-protected Rserve instance to be running (not provided by this library) 38 | and the credentials (username and password to be configured as described above) 39 | 40 | Installation 41 | --------- 42 | 43 | Using without composer : 44 | include src/autoload.php in your project 45 | 46 | Using with composer: 47 | * run `composer require cturbelin/rserve-php:2.0.x-dev` 48 | * add `require __DIR__.'/../vendor/autoload.php';` to your project 49 | 50 | Some usage example are provided in [example](example) directory 51 | 52 | Using Login Authorization 53 | ------------------------- 54 | Usage is the same as the vanilla usage, except for the constructor 55 | ```php 56 | $cnx = new \Sentiweb\Rserve\Connection('myserverhost', 6311, ['username'=>username,'password'=>password]) 57 | ``` 58 | 59 | Parsers 60 | ----- 61 | 62 | Results provided by R could be handled using several parsers 63 | 64 | - NativeArray 65 | Translates R structure into php simple arrays. It is useful to get simple values from R 66 | 67 | - Wrapped array 68 | Using NativeArray with parameters `array("wrapper"=>true)` in contructor return object 69 | with attributes of R objects. 70 | The result object can be used as an array and provides methods to access R object attributes 71 | 72 | - Debug 73 | Translates R response to structure useful for debugging 74 | 75 | - REXP 76 | Translates R response into REXP classes 77 | 78 | 79 | Async Mode 80 | ----------- 81 | 82 | Several functions allow to use connection in async mode 83 | 84 | * getSocket() to get the socket an set some options 85 | * setAsync() allow to set the async mode 86 | * getResults($parser) : get and parse the results after a call to evalString() in async mode 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cturbelin/rserve-php", 3 | "type": "library", 4 | "description": "Rserve client library for PHP", 5 | "keywords": ["R","Rserve","Stats"], 6 | "homepage": "https://github.com/cturbelin/rserve-php", 7 | "licence": "LGPL-2.1", 8 | "authors": [ 9 | { 10 | "name": "Clément Turbelin", 11 | "email": "clement.turbelin@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.4" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^9", 19 | "symplify/easy-coding-standard": "^12.0" 20 | }, 21 | "autoload": { 22 | "files": ["src/lib/helpers.php"], 23 | "psr-4": { 24 | "Sentiweb\\Rserve\\": "src/", 25 | "Sentiweb\\Rserve\\Tests\\":"tests/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4" : { 30 | "Sentiweb\\Rserve\\Tests\\":"tests/" 31 | } 32 | }, 33 | "scripts": { 34 | "test": [ 35 | "parallel-lint . --exclude vendor --exclude doc", 36 | "phpunit --verbose" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "01af730bf1665ae13aa8da3f38260719", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.5.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", 21 | "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "doctrine/coding-standard": "^9 || ^11", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpbench/phpbench": "^0.16 || ^1", 32 | "phpstan/phpstan": "^1.4", 33 | "phpstan/phpstan-phpunit": "^1", 34 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 35 | "vimeo/psalm": "^4.30 || ^5.4" 36 | }, 37 | "type": "library", 38 | "autoload": { 39 | "psr-4": { 40 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Marco Pivetta", 50 | "email": "ocramius@gmail.com", 51 | "homepage": "https://ocramius.github.io/" 52 | } 53 | ], 54 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 55 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 56 | "keywords": [ 57 | "constructor", 58 | "instantiate" 59 | ], 60 | "support": { 61 | "issues": "https://github.com/doctrine/instantiator/issues", 62 | "source": "https://github.com/doctrine/instantiator/tree/1.5.0" 63 | }, 64 | "funding": [ 65 | { 66 | "url": "https://www.doctrine-project.org/sponsorship.html", 67 | "type": "custom" 68 | }, 69 | { 70 | "url": "https://www.patreon.com/phpdoctrine", 71 | "type": "patreon" 72 | }, 73 | { 74 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 75 | "type": "tidelift" 76 | } 77 | ], 78 | "time": "2022-12-30T00:15:36+00:00" 79 | }, 80 | { 81 | "name": "myclabs/deep-copy", 82 | "version": "1.11.1", 83 | "source": { 84 | "type": "git", 85 | "url": "https://github.com/myclabs/DeepCopy.git", 86 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 87 | }, 88 | "dist": { 89 | "type": "zip", 90 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 91 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 92 | "shasum": "" 93 | }, 94 | "require": { 95 | "php": "^7.1 || ^8.0" 96 | }, 97 | "conflict": { 98 | "doctrine/collections": "<1.6.8", 99 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 100 | }, 101 | "require-dev": { 102 | "doctrine/collections": "^1.6.8", 103 | "doctrine/common": "^2.13.3 || ^3.2.2", 104 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 105 | }, 106 | "type": "library", 107 | "autoload": { 108 | "files": [ 109 | "src/DeepCopy/deep_copy.php" 110 | ], 111 | "psr-4": { 112 | "DeepCopy\\": "src/DeepCopy/" 113 | } 114 | }, 115 | "notification-url": "https://packagist.org/downloads/", 116 | "license": [ 117 | "MIT" 118 | ], 119 | "description": "Create deep copies (clones) of your objects", 120 | "keywords": [ 121 | "clone", 122 | "copy", 123 | "duplicate", 124 | "object", 125 | "object graph" 126 | ], 127 | "support": { 128 | "issues": "https://github.com/myclabs/DeepCopy/issues", 129 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 130 | }, 131 | "funding": [ 132 | { 133 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 134 | "type": "tidelift" 135 | } 136 | ], 137 | "time": "2023-03-08T13:26:56+00:00" 138 | }, 139 | { 140 | "name": "nikic/php-parser", 141 | "version": "v4.16.0", 142 | "source": { 143 | "type": "git", 144 | "url": "https://github.com/nikic/PHP-Parser.git", 145 | "reference": "19526a33fb561ef417e822e85f08a00db4059c17" 146 | }, 147 | "dist": { 148 | "type": "zip", 149 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", 150 | "reference": "19526a33fb561ef417e822e85f08a00db4059c17", 151 | "shasum": "" 152 | }, 153 | "require": { 154 | "ext-tokenizer": "*", 155 | "php": ">=7.0" 156 | }, 157 | "require-dev": { 158 | "ircmaxell/php-yacc": "^0.0.7", 159 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 160 | }, 161 | "bin": [ 162 | "bin/php-parse" 163 | ], 164 | "type": "library", 165 | "extra": { 166 | "branch-alias": { 167 | "dev-master": "4.9-dev" 168 | } 169 | }, 170 | "autoload": { 171 | "psr-4": { 172 | "PhpParser\\": "lib/PhpParser" 173 | } 174 | }, 175 | "notification-url": "https://packagist.org/downloads/", 176 | "license": [ 177 | "BSD-3-Clause" 178 | ], 179 | "authors": [ 180 | { 181 | "name": "Nikita Popov" 182 | } 183 | ], 184 | "description": "A PHP parser written in PHP", 185 | "keywords": [ 186 | "parser", 187 | "php" 188 | ], 189 | "support": { 190 | "issues": "https://github.com/nikic/PHP-Parser/issues", 191 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" 192 | }, 193 | "time": "2023-06-25T14:52:30+00:00" 194 | }, 195 | { 196 | "name": "phar-io/manifest", 197 | "version": "2.0.3", 198 | "source": { 199 | "type": "git", 200 | "url": "https://github.com/phar-io/manifest.git", 201 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 202 | }, 203 | "dist": { 204 | "type": "zip", 205 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 206 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 207 | "shasum": "" 208 | }, 209 | "require": { 210 | "ext-dom": "*", 211 | "ext-phar": "*", 212 | "ext-xmlwriter": "*", 213 | "phar-io/version": "^3.0.1", 214 | "php": "^7.2 || ^8.0" 215 | }, 216 | "type": "library", 217 | "extra": { 218 | "branch-alias": { 219 | "dev-master": "2.0.x-dev" 220 | } 221 | }, 222 | "autoload": { 223 | "classmap": [ 224 | "src/" 225 | ] 226 | }, 227 | "notification-url": "https://packagist.org/downloads/", 228 | "license": [ 229 | "BSD-3-Clause" 230 | ], 231 | "authors": [ 232 | { 233 | "name": "Arne Blankerts", 234 | "email": "arne@blankerts.de", 235 | "role": "Developer" 236 | }, 237 | { 238 | "name": "Sebastian Heuer", 239 | "email": "sebastian@phpeople.de", 240 | "role": "Developer" 241 | }, 242 | { 243 | "name": "Sebastian Bergmann", 244 | "email": "sebastian@phpunit.de", 245 | "role": "Developer" 246 | } 247 | ], 248 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 249 | "support": { 250 | "issues": "https://github.com/phar-io/manifest/issues", 251 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 252 | }, 253 | "time": "2021-07-20T11:28:43+00:00" 254 | }, 255 | { 256 | "name": "phar-io/version", 257 | "version": "3.2.1", 258 | "source": { 259 | "type": "git", 260 | "url": "https://github.com/phar-io/version.git", 261 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 262 | }, 263 | "dist": { 264 | "type": "zip", 265 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 266 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 267 | "shasum": "" 268 | }, 269 | "require": { 270 | "php": "^7.2 || ^8.0" 271 | }, 272 | "type": "library", 273 | "autoload": { 274 | "classmap": [ 275 | "src/" 276 | ] 277 | }, 278 | "notification-url": "https://packagist.org/downloads/", 279 | "license": [ 280 | "BSD-3-Clause" 281 | ], 282 | "authors": [ 283 | { 284 | "name": "Arne Blankerts", 285 | "email": "arne@blankerts.de", 286 | "role": "Developer" 287 | }, 288 | { 289 | "name": "Sebastian Heuer", 290 | "email": "sebastian@phpeople.de", 291 | "role": "Developer" 292 | }, 293 | { 294 | "name": "Sebastian Bergmann", 295 | "email": "sebastian@phpunit.de", 296 | "role": "Developer" 297 | } 298 | ], 299 | "description": "Library for handling version information and constraints", 300 | "support": { 301 | "issues": "https://github.com/phar-io/version/issues", 302 | "source": "https://github.com/phar-io/version/tree/3.2.1" 303 | }, 304 | "time": "2022-02-21T01:04:05+00:00" 305 | }, 306 | { 307 | "name": "phpunit/php-code-coverage", 308 | "version": "9.2.27", 309 | "source": { 310 | "type": "git", 311 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 312 | "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" 313 | }, 314 | "dist": { 315 | "type": "zip", 316 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", 317 | "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", 318 | "shasum": "" 319 | }, 320 | "require": { 321 | "ext-dom": "*", 322 | "ext-libxml": "*", 323 | "ext-xmlwriter": "*", 324 | "nikic/php-parser": "^4.15", 325 | "php": ">=7.3", 326 | "phpunit/php-file-iterator": "^3.0.3", 327 | "phpunit/php-text-template": "^2.0.2", 328 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 329 | "sebastian/complexity": "^2.0", 330 | "sebastian/environment": "^5.1.2", 331 | "sebastian/lines-of-code": "^1.0.3", 332 | "sebastian/version": "^3.0.1", 333 | "theseer/tokenizer": "^1.2.0" 334 | }, 335 | "require-dev": { 336 | "phpunit/phpunit": "^9.3" 337 | }, 338 | "suggest": { 339 | "ext-pcov": "PHP extension that provides line coverage", 340 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 341 | }, 342 | "type": "library", 343 | "extra": { 344 | "branch-alias": { 345 | "dev-master": "9.2-dev" 346 | } 347 | }, 348 | "autoload": { 349 | "classmap": [ 350 | "src/" 351 | ] 352 | }, 353 | "notification-url": "https://packagist.org/downloads/", 354 | "license": [ 355 | "BSD-3-Clause" 356 | ], 357 | "authors": [ 358 | { 359 | "name": "Sebastian Bergmann", 360 | "email": "sebastian@phpunit.de", 361 | "role": "lead" 362 | } 363 | ], 364 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 365 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 366 | "keywords": [ 367 | "coverage", 368 | "testing", 369 | "xunit" 370 | ], 371 | "support": { 372 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 373 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", 374 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" 375 | }, 376 | "funding": [ 377 | { 378 | "url": "https://github.com/sebastianbergmann", 379 | "type": "github" 380 | } 381 | ], 382 | "time": "2023-07-26T13:44:30+00:00" 383 | }, 384 | { 385 | "name": "phpunit/php-file-iterator", 386 | "version": "3.0.6", 387 | "source": { 388 | "type": "git", 389 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 390 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 391 | }, 392 | "dist": { 393 | "type": "zip", 394 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 395 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 396 | "shasum": "" 397 | }, 398 | "require": { 399 | "php": ">=7.3" 400 | }, 401 | "require-dev": { 402 | "phpunit/phpunit": "^9.3" 403 | }, 404 | "type": "library", 405 | "extra": { 406 | "branch-alias": { 407 | "dev-master": "3.0-dev" 408 | } 409 | }, 410 | "autoload": { 411 | "classmap": [ 412 | "src/" 413 | ] 414 | }, 415 | "notification-url": "https://packagist.org/downloads/", 416 | "license": [ 417 | "BSD-3-Clause" 418 | ], 419 | "authors": [ 420 | { 421 | "name": "Sebastian Bergmann", 422 | "email": "sebastian@phpunit.de", 423 | "role": "lead" 424 | } 425 | ], 426 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 427 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 428 | "keywords": [ 429 | "filesystem", 430 | "iterator" 431 | ], 432 | "support": { 433 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 434 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 435 | }, 436 | "funding": [ 437 | { 438 | "url": "https://github.com/sebastianbergmann", 439 | "type": "github" 440 | } 441 | ], 442 | "time": "2021-12-02T12:48:52+00:00" 443 | }, 444 | { 445 | "name": "phpunit/php-invoker", 446 | "version": "3.1.1", 447 | "source": { 448 | "type": "git", 449 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 450 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 451 | }, 452 | "dist": { 453 | "type": "zip", 454 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 455 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 456 | "shasum": "" 457 | }, 458 | "require": { 459 | "php": ">=7.3" 460 | }, 461 | "require-dev": { 462 | "ext-pcntl": "*", 463 | "phpunit/phpunit": "^9.3" 464 | }, 465 | "suggest": { 466 | "ext-pcntl": "*" 467 | }, 468 | "type": "library", 469 | "extra": { 470 | "branch-alias": { 471 | "dev-master": "3.1-dev" 472 | } 473 | }, 474 | "autoload": { 475 | "classmap": [ 476 | "src/" 477 | ] 478 | }, 479 | "notification-url": "https://packagist.org/downloads/", 480 | "license": [ 481 | "BSD-3-Clause" 482 | ], 483 | "authors": [ 484 | { 485 | "name": "Sebastian Bergmann", 486 | "email": "sebastian@phpunit.de", 487 | "role": "lead" 488 | } 489 | ], 490 | "description": "Invoke callables with a timeout", 491 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 492 | "keywords": [ 493 | "process" 494 | ], 495 | "support": { 496 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 497 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 498 | }, 499 | "funding": [ 500 | { 501 | "url": "https://github.com/sebastianbergmann", 502 | "type": "github" 503 | } 504 | ], 505 | "time": "2020-09-28T05:58:55+00:00" 506 | }, 507 | { 508 | "name": "phpunit/php-text-template", 509 | "version": "2.0.4", 510 | "source": { 511 | "type": "git", 512 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 513 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 514 | }, 515 | "dist": { 516 | "type": "zip", 517 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 518 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 519 | "shasum": "" 520 | }, 521 | "require": { 522 | "php": ">=7.3" 523 | }, 524 | "require-dev": { 525 | "phpunit/phpunit": "^9.3" 526 | }, 527 | "type": "library", 528 | "extra": { 529 | "branch-alias": { 530 | "dev-master": "2.0-dev" 531 | } 532 | }, 533 | "autoload": { 534 | "classmap": [ 535 | "src/" 536 | ] 537 | }, 538 | "notification-url": "https://packagist.org/downloads/", 539 | "license": [ 540 | "BSD-3-Clause" 541 | ], 542 | "authors": [ 543 | { 544 | "name": "Sebastian Bergmann", 545 | "email": "sebastian@phpunit.de", 546 | "role": "lead" 547 | } 548 | ], 549 | "description": "Simple template engine.", 550 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 551 | "keywords": [ 552 | "template" 553 | ], 554 | "support": { 555 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 556 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 557 | }, 558 | "funding": [ 559 | { 560 | "url": "https://github.com/sebastianbergmann", 561 | "type": "github" 562 | } 563 | ], 564 | "time": "2020-10-26T05:33:50+00:00" 565 | }, 566 | { 567 | "name": "phpunit/php-timer", 568 | "version": "5.0.3", 569 | "source": { 570 | "type": "git", 571 | "url": "https://github.com/sebastianbergmann/php-timer.git", 572 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 573 | }, 574 | "dist": { 575 | "type": "zip", 576 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 577 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 578 | "shasum": "" 579 | }, 580 | "require": { 581 | "php": ">=7.3" 582 | }, 583 | "require-dev": { 584 | "phpunit/phpunit": "^9.3" 585 | }, 586 | "type": "library", 587 | "extra": { 588 | "branch-alias": { 589 | "dev-master": "5.0-dev" 590 | } 591 | }, 592 | "autoload": { 593 | "classmap": [ 594 | "src/" 595 | ] 596 | }, 597 | "notification-url": "https://packagist.org/downloads/", 598 | "license": [ 599 | "BSD-3-Clause" 600 | ], 601 | "authors": [ 602 | { 603 | "name": "Sebastian Bergmann", 604 | "email": "sebastian@phpunit.de", 605 | "role": "lead" 606 | } 607 | ], 608 | "description": "Utility class for timing", 609 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 610 | "keywords": [ 611 | "timer" 612 | ], 613 | "support": { 614 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 615 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 616 | }, 617 | "funding": [ 618 | { 619 | "url": "https://github.com/sebastianbergmann", 620 | "type": "github" 621 | } 622 | ], 623 | "time": "2020-10-26T13:16:10+00:00" 624 | }, 625 | { 626 | "name": "phpunit/phpunit", 627 | "version": "9.6.10", 628 | "source": { 629 | "type": "git", 630 | "url": "https://github.com/sebastianbergmann/phpunit.git", 631 | "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" 632 | }, 633 | "dist": { 634 | "type": "zip", 635 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", 636 | "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", 637 | "shasum": "" 638 | }, 639 | "require": { 640 | "doctrine/instantiator": "^1.3.1 || ^2", 641 | "ext-dom": "*", 642 | "ext-json": "*", 643 | "ext-libxml": "*", 644 | "ext-mbstring": "*", 645 | "ext-xml": "*", 646 | "ext-xmlwriter": "*", 647 | "myclabs/deep-copy": "^1.10.1", 648 | "phar-io/manifest": "^2.0.3", 649 | "phar-io/version": "^3.0.2", 650 | "php": ">=7.3", 651 | "phpunit/php-code-coverage": "^9.2.13", 652 | "phpunit/php-file-iterator": "^3.0.5", 653 | "phpunit/php-invoker": "^3.1.1", 654 | "phpunit/php-text-template": "^2.0.3", 655 | "phpunit/php-timer": "^5.0.2", 656 | "sebastian/cli-parser": "^1.0.1", 657 | "sebastian/code-unit": "^1.0.6", 658 | "sebastian/comparator": "^4.0.8", 659 | "sebastian/diff": "^4.0.3", 660 | "sebastian/environment": "^5.1.3", 661 | "sebastian/exporter": "^4.0.5", 662 | "sebastian/global-state": "^5.0.1", 663 | "sebastian/object-enumerator": "^4.0.3", 664 | "sebastian/resource-operations": "^3.0.3", 665 | "sebastian/type": "^3.2", 666 | "sebastian/version": "^3.0.2" 667 | }, 668 | "suggest": { 669 | "ext-soap": "To be able to generate mocks based on WSDL files", 670 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 671 | }, 672 | "bin": [ 673 | "phpunit" 674 | ], 675 | "type": "library", 676 | "extra": { 677 | "branch-alias": { 678 | "dev-master": "9.6-dev" 679 | } 680 | }, 681 | "autoload": { 682 | "files": [ 683 | "src/Framework/Assert/Functions.php" 684 | ], 685 | "classmap": [ 686 | "src/" 687 | ] 688 | }, 689 | "notification-url": "https://packagist.org/downloads/", 690 | "license": [ 691 | "BSD-3-Clause" 692 | ], 693 | "authors": [ 694 | { 695 | "name": "Sebastian Bergmann", 696 | "email": "sebastian@phpunit.de", 697 | "role": "lead" 698 | } 699 | ], 700 | "description": "The PHP Unit Testing framework.", 701 | "homepage": "https://phpunit.de/", 702 | "keywords": [ 703 | "phpunit", 704 | "testing", 705 | "xunit" 706 | ], 707 | "support": { 708 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 709 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 710 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" 711 | }, 712 | "funding": [ 713 | { 714 | "url": "https://phpunit.de/sponsors.html", 715 | "type": "custom" 716 | }, 717 | { 718 | "url": "https://github.com/sebastianbergmann", 719 | "type": "github" 720 | }, 721 | { 722 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 723 | "type": "tidelift" 724 | } 725 | ], 726 | "time": "2023-07-10T04:04:23+00:00" 727 | }, 728 | { 729 | "name": "sebastian/cli-parser", 730 | "version": "1.0.1", 731 | "source": { 732 | "type": "git", 733 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 734 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 735 | }, 736 | "dist": { 737 | "type": "zip", 738 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 739 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 740 | "shasum": "" 741 | }, 742 | "require": { 743 | "php": ">=7.3" 744 | }, 745 | "require-dev": { 746 | "phpunit/phpunit": "^9.3" 747 | }, 748 | "type": "library", 749 | "extra": { 750 | "branch-alias": { 751 | "dev-master": "1.0-dev" 752 | } 753 | }, 754 | "autoload": { 755 | "classmap": [ 756 | "src/" 757 | ] 758 | }, 759 | "notification-url": "https://packagist.org/downloads/", 760 | "license": [ 761 | "BSD-3-Clause" 762 | ], 763 | "authors": [ 764 | { 765 | "name": "Sebastian Bergmann", 766 | "email": "sebastian@phpunit.de", 767 | "role": "lead" 768 | } 769 | ], 770 | "description": "Library for parsing CLI options", 771 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 772 | "support": { 773 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 774 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 775 | }, 776 | "funding": [ 777 | { 778 | "url": "https://github.com/sebastianbergmann", 779 | "type": "github" 780 | } 781 | ], 782 | "time": "2020-09-28T06:08:49+00:00" 783 | }, 784 | { 785 | "name": "sebastian/code-unit", 786 | "version": "1.0.8", 787 | "source": { 788 | "type": "git", 789 | "url": "https://github.com/sebastianbergmann/code-unit.git", 790 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 791 | }, 792 | "dist": { 793 | "type": "zip", 794 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 795 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 796 | "shasum": "" 797 | }, 798 | "require": { 799 | "php": ">=7.3" 800 | }, 801 | "require-dev": { 802 | "phpunit/phpunit": "^9.3" 803 | }, 804 | "type": "library", 805 | "extra": { 806 | "branch-alias": { 807 | "dev-master": "1.0-dev" 808 | } 809 | }, 810 | "autoload": { 811 | "classmap": [ 812 | "src/" 813 | ] 814 | }, 815 | "notification-url": "https://packagist.org/downloads/", 816 | "license": [ 817 | "BSD-3-Clause" 818 | ], 819 | "authors": [ 820 | { 821 | "name": "Sebastian Bergmann", 822 | "email": "sebastian@phpunit.de", 823 | "role": "lead" 824 | } 825 | ], 826 | "description": "Collection of value objects that represent the PHP code units", 827 | "homepage": "https://github.com/sebastianbergmann/code-unit", 828 | "support": { 829 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 830 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 831 | }, 832 | "funding": [ 833 | { 834 | "url": "https://github.com/sebastianbergmann", 835 | "type": "github" 836 | } 837 | ], 838 | "time": "2020-10-26T13:08:54+00:00" 839 | }, 840 | { 841 | "name": "sebastian/code-unit-reverse-lookup", 842 | "version": "2.0.3", 843 | "source": { 844 | "type": "git", 845 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 846 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 847 | }, 848 | "dist": { 849 | "type": "zip", 850 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 851 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 852 | "shasum": "" 853 | }, 854 | "require": { 855 | "php": ">=7.3" 856 | }, 857 | "require-dev": { 858 | "phpunit/phpunit": "^9.3" 859 | }, 860 | "type": "library", 861 | "extra": { 862 | "branch-alias": { 863 | "dev-master": "2.0-dev" 864 | } 865 | }, 866 | "autoload": { 867 | "classmap": [ 868 | "src/" 869 | ] 870 | }, 871 | "notification-url": "https://packagist.org/downloads/", 872 | "license": [ 873 | "BSD-3-Clause" 874 | ], 875 | "authors": [ 876 | { 877 | "name": "Sebastian Bergmann", 878 | "email": "sebastian@phpunit.de" 879 | } 880 | ], 881 | "description": "Looks up which function or method a line of code belongs to", 882 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 883 | "support": { 884 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 885 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 886 | }, 887 | "funding": [ 888 | { 889 | "url": "https://github.com/sebastianbergmann", 890 | "type": "github" 891 | } 892 | ], 893 | "time": "2020-09-28T05:30:19+00:00" 894 | }, 895 | { 896 | "name": "sebastian/comparator", 897 | "version": "4.0.8", 898 | "source": { 899 | "type": "git", 900 | "url": "https://github.com/sebastianbergmann/comparator.git", 901 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 902 | }, 903 | "dist": { 904 | "type": "zip", 905 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 906 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 907 | "shasum": "" 908 | }, 909 | "require": { 910 | "php": ">=7.3", 911 | "sebastian/diff": "^4.0", 912 | "sebastian/exporter": "^4.0" 913 | }, 914 | "require-dev": { 915 | "phpunit/phpunit": "^9.3" 916 | }, 917 | "type": "library", 918 | "extra": { 919 | "branch-alias": { 920 | "dev-master": "4.0-dev" 921 | } 922 | }, 923 | "autoload": { 924 | "classmap": [ 925 | "src/" 926 | ] 927 | }, 928 | "notification-url": "https://packagist.org/downloads/", 929 | "license": [ 930 | "BSD-3-Clause" 931 | ], 932 | "authors": [ 933 | { 934 | "name": "Sebastian Bergmann", 935 | "email": "sebastian@phpunit.de" 936 | }, 937 | { 938 | "name": "Jeff Welch", 939 | "email": "whatthejeff@gmail.com" 940 | }, 941 | { 942 | "name": "Volker Dusch", 943 | "email": "github@wallbash.com" 944 | }, 945 | { 946 | "name": "Bernhard Schussek", 947 | "email": "bschussek@2bepublished.at" 948 | } 949 | ], 950 | "description": "Provides the functionality to compare PHP values for equality", 951 | "homepage": "https://github.com/sebastianbergmann/comparator", 952 | "keywords": [ 953 | "comparator", 954 | "compare", 955 | "equality" 956 | ], 957 | "support": { 958 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 959 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 960 | }, 961 | "funding": [ 962 | { 963 | "url": "https://github.com/sebastianbergmann", 964 | "type": "github" 965 | } 966 | ], 967 | "time": "2022-09-14T12:41:17+00:00" 968 | }, 969 | { 970 | "name": "sebastian/complexity", 971 | "version": "2.0.2", 972 | "source": { 973 | "type": "git", 974 | "url": "https://github.com/sebastianbergmann/complexity.git", 975 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 976 | }, 977 | "dist": { 978 | "type": "zip", 979 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 980 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 981 | "shasum": "" 982 | }, 983 | "require": { 984 | "nikic/php-parser": "^4.7", 985 | "php": ">=7.3" 986 | }, 987 | "require-dev": { 988 | "phpunit/phpunit": "^9.3" 989 | }, 990 | "type": "library", 991 | "extra": { 992 | "branch-alias": { 993 | "dev-master": "2.0-dev" 994 | } 995 | }, 996 | "autoload": { 997 | "classmap": [ 998 | "src/" 999 | ] 1000 | }, 1001 | "notification-url": "https://packagist.org/downloads/", 1002 | "license": [ 1003 | "BSD-3-Clause" 1004 | ], 1005 | "authors": [ 1006 | { 1007 | "name": "Sebastian Bergmann", 1008 | "email": "sebastian@phpunit.de", 1009 | "role": "lead" 1010 | } 1011 | ], 1012 | "description": "Library for calculating the complexity of PHP code units", 1013 | "homepage": "https://github.com/sebastianbergmann/complexity", 1014 | "support": { 1015 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1016 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1017 | }, 1018 | "funding": [ 1019 | { 1020 | "url": "https://github.com/sebastianbergmann", 1021 | "type": "github" 1022 | } 1023 | ], 1024 | "time": "2020-10-26T15:52:27+00:00" 1025 | }, 1026 | { 1027 | "name": "sebastian/diff", 1028 | "version": "4.0.5", 1029 | "source": { 1030 | "type": "git", 1031 | "url": "https://github.com/sebastianbergmann/diff.git", 1032 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" 1033 | }, 1034 | "dist": { 1035 | "type": "zip", 1036 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1037 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1038 | "shasum": "" 1039 | }, 1040 | "require": { 1041 | "php": ">=7.3" 1042 | }, 1043 | "require-dev": { 1044 | "phpunit/phpunit": "^9.3", 1045 | "symfony/process": "^4.2 || ^5" 1046 | }, 1047 | "type": "library", 1048 | "extra": { 1049 | "branch-alias": { 1050 | "dev-master": "4.0-dev" 1051 | } 1052 | }, 1053 | "autoload": { 1054 | "classmap": [ 1055 | "src/" 1056 | ] 1057 | }, 1058 | "notification-url": "https://packagist.org/downloads/", 1059 | "license": [ 1060 | "BSD-3-Clause" 1061 | ], 1062 | "authors": [ 1063 | { 1064 | "name": "Sebastian Bergmann", 1065 | "email": "sebastian@phpunit.de" 1066 | }, 1067 | { 1068 | "name": "Kore Nordmann", 1069 | "email": "mail@kore-nordmann.de" 1070 | } 1071 | ], 1072 | "description": "Diff implementation", 1073 | "homepage": "https://github.com/sebastianbergmann/diff", 1074 | "keywords": [ 1075 | "diff", 1076 | "udiff", 1077 | "unidiff", 1078 | "unified diff" 1079 | ], 1080 | "support": { 1081 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1082 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" 1083 | }, 1084 | "funding": [ 1085 | { 1086 | "url": "https://github.com/sebastianbergmann", 1087 | "type": "github" 1088 | } 1089 | ], 1090 | "time": "2023-05-07T05:35:17+00:00" 1091 | }, 1092 | { 1093 | "name": "sebastian/environment", 1094 | "version": "5.1.5", 1095 | "source": { 1096 | "type": "git", 1097 | "url": "https://github.com/sebastianbergmann/environment.git", 1098 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" 1099 | }, 1100 | "dist": { 1101 | "type": "zip", 1102 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1103 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1104 | "shasum": "" 1105 | }, 1106 | "require": { 1107 | "php": ">=7.3" 1108 | }, 1109 | "require-dev": { 1110 | "phpunit/phpunit": "^9.3" 1111 | }, 1112 | "suggest": { 1113 | "ext-posix": "*" 1114 | }, 1115 | "type": "library", 1116 | "extra": { 1117 | "branch-alias": { 1118 | "dev-master": "5.1-dev" 1119 | } 1120 | }, 1121 | "autoload": { 1122 | "classmap": [ 1123 | "src/" 1124 | ] 1125 | }, 1126 | "notification-url": "https://packagist.org/downloads/", 1127 | "license": [ 1128 | "BSD-3-Clause" 1129 | ], 1130 | "authors": [ 1131 | { 1132 | "name": "Sebastian Bergmann", 1133 | "email": "sebastian@phpunit.de" 1134 | } 1135 | ], 1136 | "description": "Provides functionality to handle HHVM/PHP environments", 1137 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1138 | "keywords": [ 1139 | "Xdebug", 1140 | "environment", 1141 | "hhvm" 1142 | ], 1143 | "support": { 1144 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1145 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" 1146 | }, 1147 | "funding": [ 1148 | { 1149 | "url": "https://github.com/sebastianbergmann", 1150 | "type": "github" 1151 | } 1152 | ], 1153 | "time": "2023-02-03T06:03:51+00:00" 1154 | }, 1155 | { 1156 | "name": "sebastian/exporter", 1157 | "version": "4.0.5", 1158 | "source": { 1159 | "type": "git", 1160 | "url": "https://github.com/sebastianbergmann/exporter.git", 1161 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 1162 | }, 1163 | "dist": { 1164 | "type": "zip", 1165 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1166 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1167 | "shasum": "" 1168 | }, 1169 | "require": { 1170 | "php": ">=7.3", 1171 | "sebastian/recursion-context": "^4.0" 1172 | }, 1173 | "require-dev": { 1174 | "ext-mbstring": "*", 1175 | "phpunit/phpunit": "^9.3" 1176 | }, 1177 | "type": "library", 1178 | "extra": { 1179 | "branch-alias": { 1180 | "dev-master": "4.0-dev" 1181 | } 1182 | }, 1183 | "autoload": { 1184 | "classmap": [ 1185 | "src/" 1186 | ] 1187 | }, 1188 | "notification-url": "https://packagist.org/downloads/", 1189 | "license": [ 1190 | "BSD-3-Clause" 1191 | ], 1192 | "authors": [ 1193 | { 1194 | "name": "Sebastian Bergmann", 1195 | "email": "sebastian@phpunit.de" 1196 | }, 1197 | { 1198 | "name": "Jeff Welch", 1199 | "email": "whatthejeff@gmail.com" 1200 | }, 1201 | { 1202 | "name": "Volker Dusch", 1203 | "email": "github@wallbash.com" 1204 | }, 1205 | { 1206 | "name": "Adam Harvey", 1207 | "email": "aharvey@php.net" 1208 | }, 1209 | { 1210 | "name": "Bernhard Schussek", 1211 | "email": "bschussek@gmail.com" 1212 | } 1213 | ], 1214 | "description": "Provides the functionality to export PHP variables for visualization", 1215 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1216 | "keywords": [ 1217 | "export", 1218 | "exporter" 1219 | ], 1220 | "support": { 1221 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1222 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 1223 | }, 1224 | "funding": [ 1225 | { 1226 | "url": "https://github.com/sebastianbergmann", 1227 | "type": "github" 1228 | } 1229 | ], 1230 | "time": "2022-09-14T06:03:37+00:00" 1231 | }, 1232 | { 1233 | "name": "sebastian/global-state", 1234 | "version": "5.0.6", 1235 | "source": { 1236 | "type": "git", 1237 | "url": "https://github.com/sebastianbergmann/global-state.git", 1238 | "reference": "bde739e7565280bda77be70044ac1047bc007e34" 1239 | }, 1240 | "dist": { 1241 | "type": "zip", 1242 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", 1243 | "reference": "bde739e7565280bda77be70044ac1047bc007e34", 1244 | "shasum": "" 1245 | }, 1246 | "require": { 1247 | "php": ">=7.3", 1248 | "sebastian/object-reflector": "^2.0", 1249 | "sebastian/recursion-context": "^4.0" 1250 | }, 1251 | "require-dev": { 1252 | "ext-dom": "*", 1253 | "phpunit/phpunit": "^9.3" 1254 | }, 1255 | "suggest": { 1256 | "ext-uopz": "*" 1257 | }, 1258 | "type": "library", 1259 | "extra": { 1260 | "branch-alias": { 1261 | "dev-master": "5.0-dev" 1262 | } 1263 | }, 1264 | "autoload": { 1265 | "classmap": [ 1266 | "src/" 1267 | ] 1268 | }, 1269 | "notification-url": "https://packagist.org/downloads/", 1270 | "license": [ 1271 | "BSD-3-Clause" 1272 | ], 1273 | "authors": [ 1274 | { 1275 | "name": "Sebastian Bergmann", 1276 | "email": "sebastian@phpunit.de" 1277 | } 1278 | ], 1279 | "description": "Snapshotting of global state", 1280 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1281 | "keywords": [ 1282 | "global state" 1283 | ], 1284 | "support": { 1285 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1286 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" 1287 | }, 1288 | "funding": [ 1289 | { 1290 | "url": "https://github.com/sebastianbergmann", 1291 | "type": "github" 1292 | } 1293 | ], 1294 | "time": "2023-08-02T09:26:13+00:00" 1295 | }, 1296 | { 1297 | "name": "sebastian/lines-of-code", 1298 | "version": "1.0.3", 1299 | "source": { 1300 | "type": "git", 1301 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1302 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 1303 | }, 1304 | "dist": { 1305 | "type": "zip", 1306 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1307 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1308 | "shasum": "" 1309 | }, 1310 | "require": { 1311 | "nikic/php-parser": "^4.6", 1312 | "php": ">=7.3" 1313 | }, 1314 | "require-dev": { 1315 | "phpunit/phpunit": "^9.3" 1316 | }, 1317 | "type": "library", 1318 | "extra": { 1319 | "branch-alias": { 1320 | "dev-master": "1.0-dev" 1321 | } 1322 | }, 1323 | "autoload": { 1324 | "classmap": [ 1325 | "src/" 1326 | ] 1327 | }, 1328 | "notification-url": "https://packagist.org/downloads/", 1329 | "license": [ 1330 | "BSD-3-Clause" 1331 | ], 1332 | "authors": [ 1333 | { 1334 | "name": "Sebastian Bergmann", 1335 | "email": "sebastian@phpunit.de", 1336 | "role": "lead" 1337 | } 1338 | ], 1339 | "description": "Library for counting the lines of code in PHP source code", 1340 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1341 | "support": { 1342 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1343 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 1344 | }, 1345 | "funding": [ 1346 | { 1347 | "url": "https://github.com/sebastianbergmann", 1348 | "type": "github" 1349 | } 1350 | ], 1351 | "time": "2020-11-28T06:42:11+00:00" 1352 | }, 1353 | { 1354 | "name": "sebastian/object-enumerator", 1355 | "version": "4.0.4", 1356 | "source": { 1357 | "type": "git", 1358 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1359 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 1360 | }, 1361 | "dist": { 1362 | "type": "zip", 1363 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 1364 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 1365 | "shasum": "" 1366 | }, 1367 | "require": { 1368 | "php": ">=7.3", 1369 | "sebastian/object-reflector": "^2.0", 1370 | "sebastian/recursion-context": "^4.0" 1371 | }, 1372 | "require-dev": { 1373 | "phpunit/phpunit": "^9.3" 1374 | }, 1375 | "type": "library", 1376 | "extra": { 1377 | "branch-alias": { 1378 | "dev-master": "4.0-dev" 1379 | } 1380 | }, 1381 | "autoload": { 1382 | "classmap": [ 1383 | "src/" 1384 | ] 1385 | }, 1386 | "notification-url": "https://packagist.org/downloads/", 1387 | "license": [ 1388 | "BSD-3-Clause" 1389 | ], 1390 | "authors": [ 1391 | { 1392 | "name": "Sebastian Bergmann", 1393 | "email": "sebastian@phpunit.de" 1394 | } 1395 | ], 1396 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1397 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1398 | "support": { 1399 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1400 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 1401 | }, 1402 | "funding": [ 1403 | { 1404 | "url": "https://github.com/sebastianbergmann", 1405 | "type": "github" 1406 | } 1407 | ], 1408 | "time": "2020-10-26T13:12:34+00:00" 1409 | }, 1410 | { 1411 | "name": "sebastian/object-reflector", 1412 | "version": "2.0.4", 1413 | "source": { 1414 | "type": "git", 1415 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1416 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 1417 | }, 1418 | "dist": { 1419 | "type": "zip", 1420 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1421 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1422 | "shasum": "" 1423 | }, 1424 | "require": { 1425 | "php": ">=7.3" 1426 | }, 1427 | "require-dev": { 1428 | "phpunit/phpunit": "^9.3" 1429 | }, 1430 | "type": "library", 1431 | "extra": { 1432 | "branch-alias": { 1433 | "dev-master": "2.0-dev" 1434 | } 1435 | }, 1436 | "autoload": { 1437 | "classmap": [ 1438 | "src/" 1439 | ] 1440 | }, 1441 | "notification-url": "https://packagist.org/downloads/", 1442 | "license": [ 1443 | "BSD-3-Clause" 1444 | ], 1445 | "authors": [ 1446 | { 1447 | "name": "Sebastian Bergmann", 1448 | "email": "sebastian@phpunit.de" 1449 | } 1450 | ], 1451 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1452 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1453 | "support": { 1454 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1455 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 1456 | }, 1457 | "funding": [ 1458 | { 1459 | "url": "https://github.com/sebastianbergmann", 1460 | "type": "github" 1461 | } 1462 | ], 1463 | "time": "2020-10-26T13:14:26+00:00" 1464 | }, 1465 | { 1466 | "name": "sebastian/recursion-context", 1467 | "version": "4.0.5", 1468 | "source": { 1469 | "type": "git", 1470 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1471 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 1472 | }, 1473 | "dist": { 1474 | "type": "zip", 1475 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1476 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1477 | "shasum": "" 1478 | }, 1479 | "require": { 1480 | "php": ">=7.3" 1481 | }, 1482 | "require-dev": { 1483 | "phpunit/phpunit": "^9.3" 1484 | }, 1485 | "type": "library", 1486 | "extra": { 1487 | "branch-alias": { 1488 | "dev-master": "4.0-dev" 1489 | } 1490 | }, 1491 | "autoload": { 1492 | "classmap": [ 1493 | "src/" 1494 | ] 1495 | }, 1496 | "notification-url": "https://packagist.org/downloads/", 1497 | "license": [ 1498 | "BSD-3-Clause" 1499 | ], 1500 | "authors": [ 1501 | { 1502 | "name": "Sebastian Bergmann", 1503 | "email": "sebastian@phpunit.de" 1504 | }, 1505 | { 1506 | "name": "Jeff Welch", 1507 | "email": "whatthejeff@gmail.com" 1508 | }, 1509 | { 1510 | "name": "Adam Harvey", 1511 | "email": "aharvey@php.net" 1512 | } 1513 | ], 1514 | "description": "Provides functionality to recursively process PHP variables", 1515 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1516 | "support": { 1517 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1518 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 1519 | }, 1520 | "funding": [ 1521 | { 1522 | "url": "https://github.com/sebastianbergmann", 1523 | "type": "github" 1524 | } 1525 | ], 1526 | "time": "2023-02-03T06:07:39+00:00" 1527 | }, 1528 | { 1529 | "name": "sebastian/resource-operations", 1530 | "version": "3.0.3", 1531 | "source": { 1532 | "type": "git", 1533 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1534 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 1535 | }, 1536 | "dist": { 1537 | "type": "zip", 1538 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1539 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1540 | "shasum": "" 1541 | }, 1542 | "require": { 1543 | "php": ">=7.3" 1544 | }, 1545 | "require-dev": { 1546 | "phpunit/phpunit": "^9.0" 1547 | }, 1548 | "type": "library", 1549 | "extra": { 1550 | "branch-alias": { 1551 | "dev-master": "3.0-dev" 1552 | } 1553 | }, 1554 | "autoload": { 1555 | "classmap": [ 1556 | "src/" 1557 | ] 1558 | }, 1559 | "notification-url": "https://packagist.org/downloads/", 1560 | "license": [ 1561 | "BSD-3-Clause" 1562 | ], 1563 | "authors": [ 1564 | { 1565 | "name": "Sebastian Bergmann", 1566 | "email": "sebastian@phpunit.de" 1567 | } 1568 | ], 1569 | "description": "Provides a list of PHP built-in functions that operate on resources", 1570 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1571 | "support": { 1572 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1573 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 1574 | }, 1575 | "funding": [ 1576 | { 1577 | "url": "https://github.com/sebastianbergmann", 1578 | "type": "github" 1579 | } 1580 | ], 1581 | "time": "2020-09-28T06:45:17+00:00" 1582 | }, 1583 | { 1584 | "name": "sebastian/type", 1585 | "version": "3.2.1", 1586 | "source": { 1587 | "type": "git", 1588 | "url": "https://github.com/sebastianbergmann/type.git", 1589 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" 1590 | }, 1591 | "dist": { 1592 | "type": "zip", 1593 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 1594 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 1595 | "shasum": "" 1596 | }, 1597 | "require": { 1598 | "php": ">=7.3" 1599 | }, 1600 | "require-dev": { 1601 | "phpunit/phpunit": "^9.5" 1602 | }, 1603 | "type": "library", 1604 | "extra": { 1605 | "branch-alias": { 1606 | "dev-master": "3.2-dev" 1607 | } 1608 | }, 1609 | "autoload": { 1610 | "classmap": [ 1611 | "src/" 1612 | ] 1613 | }, 1614 | "notification-url": "https://packagist.org/downloads/", 1615 | "license": [ 1616 | "BSD-3-Clause" 1617 | ], 1618 | "authors": [ 1619 | { 1620 | "name": "Sebastian Bergmann", 1621 | "email": "sebastian@phpunit.de", 1622 | "role": "lead" 1623 | } 1624 | ], 1625 | "description": "Collection of value objects that represent the types of the PHP type system", 1626 | "homepage": "https://github.com/sebastianbergmann/type", 1627 | "support": { 1628 | "issues": "https://github.com/sebastianbergmann/type/issues", 1629 | "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" 1630 | }, 1631 | "funding": [ 1632 | { 1633 | "url": "https://github.com/sebastianbergmann", 1634 | "type": "github" 1635 | } 1636 | ], 1637 | "time": "2023-02-03T06:13:03+00:00" 1638 | }, 1639 | { 1640 | "name": "sebastian/version", 1641 | "version": "3.0.2", 1642 | "source": { 1643 | "type": "git", 1644 | "url": "https://github.com/sebastianbergmann/version.git", 1645 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 1646 | }, 1647 | "dist": { 1648 | "type": "zip", 1649 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 1650 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 1651 | "shasum": "" 1652 | }, 1653 | "require": { 1654 | "php": ">=7.3" 1655 | }, 1656 | "type": "library", 1657 | "extra": { 1658 | "branch-alias": { 1659 | "dev-master": "3.0-dev" 1660 | } 1661 | }, 1662 | "autoload": { 1663 | "classmap": [ 1664 | "src/" 1665 | ] 1666 | }, 1667 | "notification-url": "https://packagist.org/downloads/", 1668 | "license": [ 1669 | "BSD-3-Clause" 1670 | ], 1671 | "authors": [ 1672 | { 1673 | "name": "Sebastian Bergmann", 1674 | "email": "sebastian@phpunit.de", 1675 | "role": "lead" 1676 | } 1677 | ], 1678 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1679 | "homepage": "https://github.com/sebastianbergmann/version", 1680 | "support": { 1681 | "issues": "https://github.com/sebastianbergmann/version/issues", 1682 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 1683 | }, 1684 | "funding": [ 1685 | { 1686 | "url": "https://github.com/sebastianbergmann", 1687 | "type": "github" 1688 | } 1689 | ], 1690 | "time": "2020-09-28T06:39:44+00:00" 1691 | }, 1692 | { 1693 | "name": "symplify/easy-coding-standard", 1694 | "version": "12.0.6", 1695 | "source": { 1696 | "type": "git", 1697 | "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", 1698 | "reference": "7372622f5d9126ec50b0923d7eb6b778fac575a5" 1699 | }, 1700 | "dist": { 1701 | "type": "zip", 1702 | "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/7372622f5d9126ec50b0923d7eb6b778fac575a5", 1703 | "reference": "7372622f5d9126ec50b0923d7eb6b778fac575a5", 1704 | "shasum": "" 1705 | }, 1706 | "require": { 1707 | "php": ">=7.2" 1708 | }, 1709 | "conflict": { 1710 | "friendsofphp/php-cs-fixer": "<3.0", 1711 | "squizlabs/php_codesniffer": "<3.6", 1712 | "symplify/coding-standard": "<11.3" 1713 | }, 1714 | "bin": [ 1715 | "bin/ecs" 1716 | ], 1717 | "type": "library", 1718 | "autoload": { 1719 | "files": [ 1720 | "bootstrap.php" 1721 | ] 1722 | }, 1723 | "notification-url": "https://packagist.org/downloads/", 1724 | "license": [ 1725 | "MIT" 1726 | ], 1727 | "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", 1728 | "keywords": [ 1729 | "Code style", 1730 | "automation", 1731 | "fixer", 1732 | "static analysis" 1733 | ], 1734 | "support": { 1735 | "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", 1736 | "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.0.6" 1737 | }, 1738 | "funding": [ 1739 | { 1740 | "url": "https://www.paypal.me/rectorphp", 1741 | "type": "custom" 1742 | }, 1743 | { 1744 | "url": "https://github.com/tomasvotruba", 1745 | "type": "github" 1746 | } 1747 | ], 1748 | "time": "2023-07-25T17:12:00+00:00" 1749 | }, 1750 | { 1751 | "name": "theseer/tokenizer", 1752 | "version": "1.2.1", 1753 | "source": { 1754 | "type": "git", 1755 | "url": "https://github.com/theseer/tokenizer.git", 1756 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 1757 | }, 1758 | "dist": { 1759 | "type": "zip", 1760 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 1761 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 1762 | "shasum": "" 1763 | }, 1764 | "require": { 1765 | "ext-dom": "*", 1766 | "ext-tokenizer": "*", 1767 | "ext-xmlwriter": "*", 1768 | "php": "^7.2 || ^8.0" 1769 | }, 1770 | "type": "library", 1771 | "autoload": { 1772 | "classmap": [ 1773 | "src/" 1774 | ] 1775 | }, 1776 | "notification-url": "https://packagist.org/downloads/", 1777 | "license": [ 1778 | "BSD-3-Clause" 1779 | ], 1780 | "authors": [ 1781 | { 1782 | "name": "Arne Blankerts", 1783 | "email": "arne@blankerts.de", 1784 | "role": "Developer" 1785 | } 1786 | ], 1787 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1788 | "support": { 1789 | "issues": "https://github.com/theseer/tokenizer/issues", 1790 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 1791 | }, 1792 | "funding": [ 1793 | { 1794 | "url": "https://github.com/theseer", 1795 | "type": "github" 1796 | } 1797 | ], 1798 | "time": "2021-07-28T10:34:58+00:00" 1799 | } 1800 | ], 1801 | "aliases": [], 1802 | "minimum-stability": "stable", 1803 | "stability-flags": [], 1804 | "prefer-stable": false, 1805 | "prefer-lowest": false, 1806 | "platform": { 1807 | "php": ">=7.4" 1808 | }, 1809 | "platform-dev": [], 1810 | "plugin-api-version": "2.0.0" 1811 | } 1812 | -------------------------------------------------------------------------------- /doc/QAP1_protocol.txt: -------------------------------------------------------------------------------- 1 | QAP1 Protocol 2 | ============================== 3 | 4 | Any interested developers are welcome to improve Rserve since it is released under GPL. You can download the sources from the download section. As you can easily see from the directory structure I hold the sources in a CVS repository, therefore anyone who wants to contribute to the development should send me an e-mail (Simon.Urbanek@r-project.org) in order to obtain access to the CVS. 5 | 6 | Technical Documentation for Rserve 7 | ---------------------------------- 8 | This document describes the protocols and structures used by Rserve (version 0.1-9). This information is helpful for implementing Rserve clients. 9 | Rserve communication is performed over any reliable connection-oriented protocol (usually TCP/IP; Rserve 0.1-9 supports TCP/IP and local unix sockets). After connection is established, the server sends 32 bytes representing the ID-string defining the capabilities of the server. Each attribute of the ID-string is 4 bytes long and is meant to be user- readable (i.e. use no special characters), and it's a good idea to make "\r\n\r\n" the last attribute. 10 | 11 | the ID string must be of the form: 12 | 13 | [0] "Rsrv" - R-server ID signature 14 | [4] "0100" - version of the R server protocol 15 | [8] "QAP1" - protocol used for communication (here Quad Attributes Packets v1) 16 | [12] any additional attributes follow. \r\n and '-' are ignored. 17 | optional attributes (in any order; it is legitimate to put dummy attributes, like "----" or " " between attributes): 18 | "R151" - version of R (here 1.5.1) 19 | "ARpt" - authorization required (here "pt"=plain text, "uc"=unix crypt) 20 | connection will be closed 21 | if the first packet is not CMD_login. 22 | if more AR.. methods are specified, then client is free to 23 | use the one he supports (usually the most secure) 24 | "K***" - key if encoded authentification is challenged (*** is the key) 25 | for unix crypt the first two letters of the key are the salt 26 | required by the server */ 27 | The protocol specified in the third attribute (here QAP1) is used immediately after the ID string was transmitted. 28 | 29 | QAP1 message oriented protocol 30 | ----------------------------- 31 | 32 | QAP1 (quad attributes protocol v1) is a message oriented protocol, i.e. the initiating side (here the client) sends a message and awaits a response. The message contains both the action to be taken and any necessary data. The response contains a response code and any associated data. Every message consists of a header and data part (which can be empty). The header is structured as follows: 33 | 34 | [0] (int) command 35 | [4] (int) length of the message (bits 0-31) 36 | [8] (int) offset of the data part 37 | [12] (int) length of the message (bits 32-63) 38 | command specifies the request or response type. 39 | length specifies the number of bytes belonging to this message (excluding the header). 40 | offset specifies the offset of the data part, where 0 means directly after the header (which is normally the case) 41 | length2 high bits of the length (must be 0 if the packet size is smaller than 4GB) 42 | The header must always be transmitted en-block. Data part can be split into packets of an arbitrary size. Each message consists of 16 bytes (the header) plus data. Therefore a message consists of length+16 bytes (where length is the size of the data payload). 43 | 44 | The data part contains any additional parameters that are send along with the command. Each parameter consists of a 4-byte header: 45 | 46 | [0] (byte) type 47 | [1] (24-bit int) length 48 | Types used by the current Rserve implementation (for list of all supported types see Rsrv.h): 49 | * DT_INT (4 bytes) integer 50 | * DT_STRING (n bytes) null terminated string 51 | * DT_BYTESTREAM (n bytes) any binary data 52 | * DT_SEXP R's encoded SEXP, see below 53 | all int and double entries throughout the transfer are encoded in Intel-endianess format: 54 | int=0x12345678 -> char[4]=(0x78,0x56,x34,0x12) functions/macros for converting from native to protocol format are available in Rsrv.h. 55 | Commands supported by Rserve 56 | 57 | Supported commands: 58 | 59 | command parameters | response data 60 | 61 | CMD_login DT_STRING | - 62 | CMD_voidEval DT_STRING | - 63 | CMD_eval DT_STRING | DT_SEXP 64 | CMD_shutdown [DT_STRING] | - 65 | CMD_openFile DT_STRING | - 66 | CMD_createFile DT_STRING | - 67 | CMD_closeFile - | - 68 | CMD_readFile [DT_INT] | DT_BYTESTREAM 69 | CMD_writeFile DT_BYTESTREAM | - 70 | CMD_removeFile DT_STRING | - 71 | CMD_setSEXP DT_STRING, | - 72 | DT_SEXP 73 | CMD_assignSEXP DT_STRING, | - 74 | DT_SEXP 75 | CMD_setBufferSize DT_INT | - 76 | CMD_setEncoding DT_STRING | - (since 0.5-3) 77 | CMD_ctrlEval DT_STRING | - (since 0.6-0) 78 | CMD_ctrlSource DT_STRING | - (since 0.6-0) 79 | CMD_ctrlShutdown - | - (since 0.6-0) 80 | 81 | (Parameters in brackets [] are optional) 82 | 83 | Responses: 84 | The CMD_RESP mask is set for all responses. Each response consists of the response command (RESP_OK or RESP_ERR - least significant 24 bit) and the status code (most significant 8 bits). For a list of all currently supported status codes see ERR_... in Rsrv.h. 85 | 86 | Note: Commands with four highest bits set (0xf0) are reserved as internal/special and their data payload does not follow the regular pattern. They should not be used by clients. 87 | 88 | Encoding of SEXP R expression 89 | ------------------------------ 90 | 91 | R SEXP value (DT_SEXP) are recursively encoded in a similar way as the parameter attributes. Each SEXP consists of a 4-byte header and the actual contents. The header is of the form: 92 | 93 | [0] (byte) eXpression Type 94 | [1] (24-bit int) length 95 | The expression type consists of the actual type (least significant 6 bits) and attributes. Follwing expression types are supported: 96 | XT_NULL data: - 97 | - XT_INT data: (4) int 98 | - XT_DOUBLE data: (8) double 99 | - XT_STR data: (n) char null-term. strg. 100 | - XT_LANG data: same as XT_LIST 101 | - XT_SYM data: (n) char symbol name 102 | - XT_BOOL data: (1) byte boolean 103 | (1=TRUE, 0=FALSE, 2=NA) 104 | + XT_S4 data: - 105 | 106 | XT_VECTOR data: (n*?) SEXP 107 | - XT_LIST data: SEXP head, SEXP vals, [SEXP tag] 108 | XT_CLOS data: SEXP formals, SEXP body 109 | + XT_SYMNAME data: same as XT_STR 110 | + XT_LIST_NOTAG data: same as XT_VECTOR 111 | + XT_LIST_TAG data: SEXP tag, SEXP value, ... 112 | + XT_LANG_NOTAG data: same as XT_LIST_NOTAG (LANGSXP) 113 | + XT_LANG_TAG data: same as XT_LIST_TAG (LANGSXP) 114 | + XT_VECTOR_EXP data: same as XT_VECTOR (EXPSXP) 115 | 116 | XT_ARRAY_INT data: (n*4) int,int,.. 117 | XT_ARRAY_DOUBLE data: (n*8) double,double,.. 118 | XT_ARRAY_STR data: (?) string,string,.. 119 | XT_ARRAY_BOOL data: (n) byte,byte,.. 120 | + XT_RAW data: (1) int n (n) byte,byte,.. 121 | + XT_ARRAY_CPLX data: (n) double(re),double(im),.. 122 | 123 | XT_UNKNOWN data: (4) int - SEXP type as defined in R 124 | 125 | - = removed in protocol 0103 (Rserve 0.5) 126 | + = new since protocol 0103 (Rserve 0.5) 127 | Attributes: 128 | XT_HAS_ATTR - if this flag is set then the SEXP has an attribute list which is stored before the actual expression. In this case the layout looks as follows: 129 | [0] (4) header SEXP: len=4+m+n, XT_HAS_ATTR is set 130 | [4] (4) header attribute SEXP: len=n 131 | [8] (n) data attribute SEXP 132 | [8+n] (m) data SEXP 133 | -------------------------------------------------------------------------------- /doc/QAP_message.txt: -------------------------------------------------------------------------------- 1 | 2 | +---------------------------------------------------------------+ 3 | | Header (16 bits) | 4 | | + 4 bytes + 4 bytes + 4 bytes + 4 bytes + | 5 | | +------------+------------+-----------+-----------+ | 6 | | | Command | length-low | offset | length-hi | | 7 | | +------------+------------+-----------+-----------+ | 8 | +---------------------------------------------------------------+ 9 | | Data Part | 10 | | +-------------------------------------------------------+ | 11 | | | Header (4 bytes) | | 12 | | | Type (1 byte) : DT_* Length (3 bytes) | | 13 | | +-------------------------------------------------------+ | 14 | | + Data (length bytes) | | 15 | | +-------------------------------------------------------+ | 16 | | +-------------------------------------------------------+ | 17 | | | Header (4 bytes) | | 18 | | | Type (1 byte) : DT_* Length (3 bytes) | | 19 | | +-------------------------------------------------------+ | 20 | | + Data (length bytes) | | 21 | | +-------------------------------------------------------+ | 22 | -------------------------------------------------------------------------------- /doc/original_version_simple.php: -------------------------------------------------------------------------------- 1 | >= 8; $r .= chr($i & 255); $i >>=8; $r .= chr($i & 255); $i >>=8; $r .= chr($i & 255); return $r; } 42 | function _rserve_mkint24($i) { $r = chr($i & 255); $i >>= 8; $r .= chr($i & 255); $i >>=8; $r .= chr($i & 255); return $r; } 43 | function _rserve_flt64($buf, $o=0) { $ss = substr($buf, $o, 8); if ($machine_is_bigendian) for ($k = 0; $k < 7; $k++) $ss[7 - $k] = $buf[$o + $k]; $r = unpack("d", substr($buf, $o, 8)); return $r[1]; } 44 | 45 | function mkp_str($cmd, $string) { 46 | $n = strlen($string) + 1; $string .= chr(0); 47 | while (($n & 3) != 0) { $string .= chr(1); $n++; } 48 | return _rserve_mkint32($cmd) . _rserve_mkint32($n + 4) . _rserve_mkint32(0) . _rserve_mkint32(0) . chr(4) . _rserve_mkint24($n) . $string; 49 | } 50 | 51 | function get_rsp($socket) { 52 | $n = socket_recv($socket, $buf, 16, 0); 53 | if ($n != 16) return FALSE; 54 | $len = _rserve_int32($buf, 4); 55 | $ltg = $len; 56 | while ($ltg > 0) { 57 | $n = socket_recv($socket, $b2, $ltg, 0); 58 | if ($n > 0) { $buf .= $b2; unset($b2); $ltg -= $n; } else break; 59 | } 60 | return $buf; 61 | } 62 | 63 | // parse SEXP results -- limited implementation for now (large packets and some data types are not supported) 64 | function parse_SEXP($buf, $offset, $attr = NULL) { 65 | $r = $buf; 66 | $i = $offset; 67 | // some simple parsing - just skip attributes and assume short responses 68 | $ra = _rserve_int8($r, $i); 69 | $rl = _rserve_int24($r, $i + 1); 70 | $i += 4; 71 | $offset = $eoa = $i + $rl; 72 | // echo "[data type ".($ra & 63).", length ".$rl." with payload from ".$i." to ".$eoa."]
\n"; 73 | if (($ra & 64) == 64) { 74 | echo "sorry, long packets are not supported (yet)."; return FALSE; 75 | } 76 | if ($ra > 127) { 77 | $ra &= 127; 78 | $al = _rserve_int24($r, $i + 1); 79 | $attr = parse_SEXP($buf, $i); 80 | $i += $al + 4; 81 | } 82 | if ($ra == 0) return NULL; 83 | if ($ra == 16) { // generic vector 84 | $a = array(); 85 | while ($i < $eoa) 86 | $a[] = parse_SEXP($buf, &$i); 87 | // if the 'names' attribute is set, convert the plain array into a map 88 | if (isset($attr['names'])) { 89 | $names = $attr['names']; $na = array(); $n = count($a); 90 | for ($k = 0; $k < $n; $k++) $na[$names[$k]] = $a[$k]; 91 | return $na; 92 | } 93 | return $a; 94 | } 95 | if ($ra == 19) { // symbol 96 | $oi = $i; while ($i < $eoa && ord($r[$i]) != 0) $i++; 97 | return substr($buf, $oi, $i - $oi); 98 | } 99 | if ($ra == 20 || $ra == 22) { // pairlist w/o tags 100 | $a = array(); 101 | while ($i < $eoa) $a[] = parse_SEXP($buf, &$i); 102 | return $a; 103 | } 104 | if ($ra == 21 || $ra == 23) { // pairlist with tags 105 | $a = array(); 106 | while ($i < $eoa) { $val = parse_SEXP($buf, &$i); $tag = parse_SEXP($buf, &$i); $a[$tag] = $val; } 107 | return $a; 108 | } 109 | if ($ra == 32) { // integer array 110 | $a = array(); 111 | while ($i < $eoa) { $a[] = _rserve_int32($r, $i); $i += 4; } 112 | if (count($a) == 1) return $a[0]; 113 | return $a; 114 | } 115 | if ($ra == 33) { // double array 116 | $a = array(); 117 | while ($i < $eoa) { $a[] = _rserve_flt64($r, $i); $i += 8; } 118 | if (count($a) == 1) return $a[0]; 119 | return $a; 120 | } 121 | if ($ra == 34) { // string array 122 | $a = array(); 123 | $oi = $i; 124 | while ($i < $eoa) { 125 | if (ord($r[$i]) == 0) { 126 | $a[] = substr($r, $oi, $i - $oi); 127 | $oi = $i + 1; 128 | } 129 | $i++; 130 | } 131 | if (count($a) == 1) return $a[0]; 132 | return $a; 133 | } 134 | if ($ra == 36) { // boolean vector 135 | $n = _rserve_int32($r, $i); $i += 4; $k = 0; 136 | $a = array(); 137 | while ($k < $n) { $v = _rserve_int8($r, $i++); $a[$k++] = ($v == 1) ? TRUE : (($v == 0) ? FALSE : NULL); } 138 | if ($n == 1) return $a[0]; 139 | return $a; 140 | } 141 | if ($ra == 37) { // raw vector 142 | $len = _rserve_int32($r, $i); $i += 4; 143 | return substr($r, $i, $len); 144 | } 145 | if ($ra == 48) { // unimplemented type in Rserve 146 | $uit = _rserve_int32($r, $i); 147 | // echo "Note: result contains type #$uit unsupported by Rserve.
"; 148 | return NULL; 149 | } 150 | echo "Warning: type ".$ra." is currently not implemented in the PHP client."; 151 | return FALSE; 152 | } 153 | 154 | //------------ Rserve API functions 155 | 156 | // if port is 0 then host is interpreted as unix socket, otherwise host is the host to connect to (default is local) and port is the TCP port number (6311 is the default) 157 | function Rserve_connect($host="127.0.0.1", $port=6311) { 158 | if ($port == 0) 159 | $ok = (($socket = socket_create(AF_UNIX, SOCK_STREAM, 0)) and (socket_connect($socket, $host))); 160 | else 161 | $ok = (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) and (socket_connect($socket, $host, $port))); 162 | if ($ok) { 163 | $n = socket_recv($socket, $buf, 32, 0); 164 | if ($n < 32 || strncmp($buf, "Rsrv", 4) != 0) { 165 | echo "Invalid response from server."; 166 | return FALSE; 167 | } 168 | $rv = substr($buf, 4, 4); 169 | if (strcmp($rv, "0103") != 0) { 170 | echo "Unsupported protocol version."; 171 | return FALSE; 172 | } 173 | } else { 174 | echo "Unable to connect
".socket_strerror(socket_last_error())."
"; 175 | return FALSE; 176 | } 177 | return $socket; 178 | } 179 | 180 | function Rserve_eval($socket, $command, $attr = NULL) { 181 | $pkt = mkp_str(3, $command); 182 | socket_send($socket, $pkt, strlen($pkt), 0); 183 | $r = get_rsp($socket); 184 | $res = _rserve_int32($r); 185 | $sc = ($res >> 24) & 127; 186 | $rr = $res & 255; 187 | if ($rr != 1) { echo "eval failed with error code " . $sc; return FALSE; } 188 | if (_rserve_int8($r, 16) != 10) { echo "invalid response (expecting SEXP)"; return FALSE; } 189 | $i = 20; 190 | return parse_SEXP($r, $i, &$attr); 191 | } 192 | 193 | function Rserve_close($socket) { 194 | return socket_close($socket); 195 | } 196 | 197 | //========== FastRWeb - compatible requests - sample use of the client to behave like Rcgi in FastRWeb 198 | 199 | $root = "/var/FastRWeb"; // set to the root of your FastRWeb installation - must be absolute 200 | 201 | function process_FastRWeb() { 202 | global $root; 203 | // $req = array_merge($_GET, $_POST); 204 | $path = $_SERVER['PATH_INFO']; 205 | if (!isset($path)) { echo "No path specified."; return FALSE; } 206 | $sp = str_replace("..", "_", $path); // sanitize paths 207 | $script = "$root/web.R$sp.R"; 208 | if (!file_exists($script)) { echo "Script [$script] $sp.R does not exist."; return FALSE; } 209 | // escape dangerous characters 210 | $script = str_replace("\\", "\\\\", $script); 211 | $script = str_replace("\"", "\\\"", $script); 212 | $qs = str_replace("\\", "\\\\", $_SERVER['QUERY_STRING']); 213 | $qs = str_replace("\"", "\\\"", $qs); 214 | $s = Rserve_connect(); 215 | $r = Rserve_eval($s, "{ qs<-\"$qs\"; setwd('$root/tmp'); library(FastRWeb); .out<-''; cmd<-'html'; ct<-'text/html'; hdr<-''; pars<-list(); lapply(strsplit(strsplit(qs,\"&\")[[1]],\"=\"),function(x) pars[[x[1]]]<<-x[2]); if(exists('init') && is.function(init)) init(); as.character(try({source(\"$script\"); as.WebResult(do.call(run, pars)) },silent=TRUE))}"); 216 | Rserve_close($s); 217 | 218 | if (!is_array($r)) { // this ususally means that an erro rocurred since the returned value is jsut a string 219 | ob_end_flush(); 220 | echo $r; 221 | exit(0); 222 | } 223 | 224 | if (isset($r[2])) header("Content-type: $r[2]"); 225 | 226 | if (($r[0] == "file") or ($r[0] == "tmpfile")) { 227 | $f = fopen($r[1], "rb"); 228 | $contents = ''; 229 | while (!feof($f)) $contents .= fread($f, 8192); 230 | fclose($f); 231 | ob_end_clean(); 232 | echo $contents; 233 | if ($r[0] == "tmpfile") unlink($r[0]); 234 | exit(0); 235 | } 236 | 237 | if ($r[0] == "html") { 238 | ob_end_clean(); 239 | echo (is_array($r[1]) ? implode("\n", $r[1]) : $r[1]); 240 | exit(0); 241 | } 242 | 243 | print_r($r); 244 | 245 | ob_end_flush(); 246 | 247 | exit(0); 248 | } 249 | 250 | //--- uncomment the following line if you want this script to serve as FastRWeb handler (see FastRWeb package and IASC paper) 251 | // process_FastRWeb(); 252 | 253 | //========== user code -- example and test -- 254 | 255 | $s = Rserve_connect(); 256 | if ($s == FALSE) { 257 | echo "Connect FAILED"; 258 | } else { 259 | print_r (Rserve_eval($s, "list(str=R.version.string,foo=1:10,bar=1:5/2,logic=c(TRUE,FALSE,NA))")); 260 | echo "

"; 261 | print_r (Rserve_eval($s, "{x=rnorm(10); y=x+rnorm(10)/2; lm(y~x)}")); 262 | 263 | Rserve_close($s); 264 | } 265 | 266 | ob_end_flush(); 267 | 268 | ?> 269 | -------------------------------------------------------------------------------- /example/config.php: -------------------------------------------------------------------------------- 1 | evalString($command); 28 | //var_dump($r); 29 | } catch(Rserve_Exception $e) { 30 | var_dump($e); 31 | } 32 | 33 | echo "NativeArray\n"; 34 | $parser = new NativeArray(array('wrapper'=>true)); 35 | try { 36 | $command = 'seq(1,10, by=NA)'; 37 | $command = 'r= try({'.$command.'}, silent=T); if( inherits(r,"try-error")) { r = structure(list(message=unclass(r)), class="try-error") }; r'; 38 | $r = $cnx->evalString($command, $parser); 39 | 40 | var_dump($r); 41 | } catch(Rserve_Exception $e) { 42 | var_dump($e); 43 | } 44 | 45 | echo "REXP\n"; 46 | $parser = new REXP(); 47 | try { 48 | $command = 'seq(1,10, by=NA)'; 49 | $command = 'try({'.$command.'}, silent=T)'; 50 | $r = $cnx->evalString($command, $parser); 51 | 52 | $class = $r->getAttribute('class'); 53 | if($class) { 54 | $class = $class->getValues(); 55 | } 56 | 57 | //var_dump($class); 58 | 59 | //var_dump($r); 60 | } catch(Rserve_Exception $e) { 61 | var_dump($e); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /example/evaluator.php: -------------------------------------------------------------------------------- 1 | evaluate('seq(1,10, by=NA)'); 22 | var_dump($r); 23 | // Should have an class attribute set to "try-error" 24 | 25 | 26 | // Create evaluator with REXP parser 27 | $eval = new Evaluator($cnx, Evaluator::PARSER_REXP); 28 | $r = $eval->evaluate('seq(1,10, by=NA)'); 29 | var_dump($r); 30 | // Return an Error instance -------------------------------------------------------------------------------- /example/example1.php: -------------------------------------------------------------------------------- 1 | evalString('chisq.test(c(12,223,22,10))'); 20 | 21 | 22 | var_dump($r); 23 | 24 | // Do the same ChiSQ but using ArrayWrapper to get attributes with result 25 | $parser = new NativeArray(array('wrapper'=>true)); 26 | $r = $cnx->evalString('chisq.test(c(12,223,22,10))', $parser); 27 | 28 | var_dump($r); 29 | 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests/ParserNativeTest.php 6 | tests/REXPTest.php 7 | 8 | 9 | tests/SessionTest.php 10 | tests/LoginTest.php 11 | 12 | 13 | -------------------------------------------------------------------------------- /rserve.R: -------------------------------------------------------------------------------- 1 | # This is a example file to run a very basic Rserve instance 2 | # You can use it by running 3 | # Rscript rserve.R 4 | # It will launch Rserve and wait for connection 5 | # In some installation the R command is available 6 | # R CMD Rserve 7 | # @see Rserve doc 8 | if(length(find.package("Rserve", quiet=TRUE)) == 0) { 9 | install.packages("Rserve") 10 | } 11 | library(Rserve) 12 | run.Rserve() -------------------------------------------------------------------------------- /src/ArrayWrapper.php: -------------------------------------------------------------------------------- 1 | data = $data; 43 | $this->attr = $attributes; 44 | $this->type = $exp_type; 45 | } 46 | 47 | /** 48 | * @param string $name get the attribute named $name 49 | * @return mixed 50 | */ 51 | public function getAttr($name) 52 | { 53 | return $this->attr[$name] ?? null; 54 | } 55 | 56 | /** 57 | * Test if an attibute exists 58 | * @param string $name 59 | */ 60 | public function hasAttr($name) 61 | { 62 | return isset($this->attr[$name]); 63 | } 64 | 65 | /** 66 | * Type of the parsed expression (vector, list, etc) (@see Parser::xtName()) 67 | */ 68 | public function getType() 69 | { 70 | return $this->type; 71 | } 72 | 73 | /** 74 | * Get the attributes 75 | */ 76 | public function getAttributes() 77 | { 78 | return $this->attr; 79 | } 80 | 81 | // ArrayAccess Implementation allows array-like syntax for instances 82 | 83 | public function offsetSet($offset, $value): void 84 | { 85 | $this->data[$offset] = $value; 86 | } 87 | 88 | public function offsetExists($offset): bool 89 | { 90 | return isset($this->data[$offset]); 91 | } 92 | 93 | public function offsetUnset($offset): void 94 | { 95 | unset($this->data[$offset]); 96 | } 97 | 98 | public function offsetGet($offset) 99 | { 100 | return $this->data[$offset] ?? null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Connection.php: -------------------------------------------------------------------------------- 1 | 0xfffff0) and hence uses 56-bit length field */ 35 | const DT_LARGE = 64; 36 | 37 | const CMD_login = 0x001; 38 | const CMD_voidEval = 0x002; 39 | const CMD_eval = 0x003; 40 | const CMD_shutdown = 0x004; 41 | const CMD_openFile = 0x010; 42 | const CMD_createFile = 0x011; 43 | const CMD_closeFile = 0x012; 44 | const CMD_readFile = 0x013; 45 | const CMD_writeFile = 0x014; 46 | const CMD_removeFile = 0x015; 47 | const CMD_setSEXP = 0x020; 48 | const CMD_assignSEXP = 0x021; 49 | 50 | const CMD_setBufferSize = 0x081; 51 | const CMD_setEncoding = 0x082; 52 | 53 | const CMD_detachSession = 0x030; 54 | const CMD_detachedVoidEval = 0x031; 55 | const CMD_attachSession = 0x032; 56 | 57 | // control commands since 0.6-0 58 | const CMD_ctrlEval = 0x42; 59 | const CMD_ctrlSource = 0x45; 60 | const CMD_ctrlShutdown = 0x44; 61 | 62 | const CMD_Response = 0x10000; 63 | 64 | // errors as returned by Rserve 65 | const ERR_auth_failed = 0x41; 66 | const ERR_conn_broken = 0x42; 67 | const ERR_inv_cmd = 0x43; 68 | const ERR_inv_par = 0x44; 69 | const ERR_Rerror = 0x45; 70 | const ERR_IOerror = 0x46; 71 | const ERR_not_open = 0x47; 72 | const ERR_access_denied = 0x48; 73 | const ERR_unsupported_cmd=0x49; 74 | const ERR_unknown_cmd = 0x4a; 75 | const ERR_data_overflow = 0x4b; 76 | const ERR_object_too_big = 0x4c; 77 | const ERR_out_of_mem = 0x4d; 78 | const ERR_ctrl_closed = 0x4e; 79 | const ERR_session_busy = 0x50; 80 | const ERR_detach_failed = 0x51; 81 | 82 | public static $machine_is_bigendian = null; 83 | 84 | private static $init = false; 85 | 86 | private $host; 87 | private int $port; 88 | private $socket; 89 | private $auth_request; 90 | private $auth_method; 91 | 92 | private bool $debug; 93 | 94 | private bool $async; 95 | 96 | private ?string $username; 97 | 98 | private ?string $password; 99 | 100 | /** 101 | * Encoding to use 102 | * @var string 103 | */ 104 | private ?string $encoding; 105 | 106 | // Internal parser, used as default parser 107 | // To handle internal operations 108 | private $parser; 109 | 110 | /** 111 | * initialization of the library 112 | */ 113 | public static function init() { 114 | if( self::$init ) { 115 | return; 116 | } 117 | $m = pack('s', 1); 118 | self::$machine_is_bigendian = ($m[0] == 0); 119 | self::$init = true; 120 | } 121 | 122 | /** 123 | * @param mixed host or a Session instance or an array of parameters 124 | * @param int $port if 0 then host is interpreted as unix socket, 125 | * @param array params 126 | * 127 | * If host is an array then further arguments are ignored 128 | * (all options should be passed using this array) 129 | * 130 | * If 131 | * 132 | */ 133 | public function __construct($host=self::DEFAULT_HOST, int $port = self::DEFAULT_PORT, $params=[]) { 134 | if( !self::$init ) { 135 | self::init(); 136 | } 137 | $session = null; 138 | 139 | if( is_array($host) ) { 140 | $params = $host; 141 | $this->host = $params['host'] ?? self::DEFAULT_HOST; 142 | $this->port = (int)($params['port'] ?? self::DEFAULT_PORT); 143 | 144 | } elseif(is_object($host) AND $host instanceof Session) { 145 | $session = $host->key; 146 | $this->port = (int)$host->port; 147 | $host = $host->host; 148 | if( !$host ) { 149 | $host = self::DEFAULT_HOST; 150 | } 151 | $this->host = $host; 152 | } else { 153 | $this->host = $host; 154 | $this->port = $port; 155 | } 156 | $this->debug = (bool)($params['debug'] ?? false); 157 | $this->async = (bool)($params['async'] ?? false); 158 | $this->username = $params['username'] ?? null; 159 | $this->password = $params['password'] ?? null; 160 | $this->encoding = $params['encoding'] ?? null; 161 | 162 | // Internal parser used for basic command 163 | $this->parser = new NativeArray(); 164 | 165 | $this->openSocket($session); 166 | } 167 | 168 | /** 169 | * Open a new socket to Rserv 170 | * @return resource socket 171 | */ 172 | private function openSocket($session_key = null) { 173 | 174 | if( $this->port == 0 ) { 175 | $socket = socket_create(AF_UNIX, SOCK_STREAM, 0); 176 | } else { 177 | $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 178 | } 179 | if( !$socket ) { 180 | throw new Exception('Unable to create socket ['.socket_strerror(socket_last_error()).']'); 181 | } 182 | //socket_set_option($socket, SOL_TCP, SO_DEBUG,2); 183 | 184 | $ok = socket_connect($socket, $this->host, $this->port); 185 | if( !$ok ) { 186 | throw new Exception('Unable to connect ['.socket_strerror(socket_last_error()).']'); 187 | } 188 | $this->socket = $socket; 189 | if( !is_null($session_key) ) { 190 | // Try to resume session 191 | $n = socket_send($socket, $session_key, 32, 0); 192 | if($n < 32) { 193 | throw new Exception('Unable to send session key'); 194 | } 195 | $r = $this->getResponse(); 196 | if($r['is_error']) { 197 | $msg = $this->getErrorMessage($r['error']); 198 | throw new Exception('invalid session key : '.$msg); 199 | } 200 | return; 201 | } 202 | 203 | // No session, check handshake 204 | $buf = ''; 205 | $n = socket_recv($socket, $buf, 32, 0); 206 | if( $n < 32 || strncmp($buf, 'Rsrv', 4) != 0 ) { 207 | throw new Exception('Invalid response from server.'); 208 | } 209 | $rv = substr($buf, 4, 4); 210 | if( strcmp($rv, '0103') != 0 ) { 211 | throw new Exception('Unsupported protocol version.'); 212 | } 213 | $key=null; 214 | $this->auth_request = false; 215 | for($i = 12; $i < 32; $i += 4) { 216 | $attr = substr($buf, $i, 4); 217 | if($attr == 'ARpt') { 218 | $this->auth_request = true; 219 | $this->auth_method = 'plain'; 220 | 221 | } elseif($attr == 'ARuc') { 222 | $this->auth_request = true; 223 | $this->auth_method = 'crypt'; 224 | } 225 | if($attr[0] === 'K') { 226 | $key = substr($attr, 1, 3); 227 | } 228 | } 229 | if($this->auth_request === true) { 230 | if($this->auth_method=="plain") $this->login(); else $this->login($key); 231 | } 232 | 233 | if($this->encoding) { 234 | $this->setEncoding($this->encoding); 235 | } 236 | } 237 | 238 | /** 239 | * Allow access to socket 240 | */ 241 | public function getSocket() { 242 | return $this->socket; 243 | } 244 | 245 | /** 246 | * Set Asynchronous mode 247 | * @param bool $async 248 | */ 249 | public function setAsync($async) { 250 | $this->async = (bool)$async; 251 | } 252 | 253 | /** 254 | * 255 | * Parse a response from Rserve 256 | * @param $buf 257 | * @param Parser $parser 258 | * @return mixed parsed results 259 | */ 260 | private function parseResponse($buf, $parser=null) { 261 | $type = _rserve_int8($buf, 0); 262 | if($type != self::DT_SEXP) { // Check Data type of the packet 263 | throw new Exception('Unexpected packet Data type (expect DT_SEXP)', $buf); 264 | } 265 | $i = 4; // + 4 bytes (Data part HEADER) 266 | $r = null; 267 | if( is_null($parser) ) { 268 | $r = $this->parser->parse($buf, $i); 269 | } else { 270 | $r = $parser->parse($buf, $i); 271 | } 272 | return $r; 273 | } 274 | 275 | 276 | /** 277 | * Login to rserve 278 | * Similar to RSlogin http://rforge.net/doc/packages/RSclient/Rclient.html 279 | * Inspired from https://github.com/SurajGupta/RserveCLI2/blob/master/RServeCLI2/Qap1.cs 280 | * https://github.com/SurajGupta/RserveCLI2/blob/master/RServeCLI2/RConnection.cs 281 | * @param string $salt 282 | */ 283 | public function login($salt=null) { 284 | switch ( $this->auth_method ) 285 | { 286 | case "plain": 287 | break; 288 | case "crypt": 289 | if( !$salt ) { 290 | throw new Exception("Should pass the salt for login"); 291 | } 292 | $this->password=crypt($this->password, $salt); 293 | break; 294 | default: 295 | throw new Exception( "Could not interpret login method '{$this->auth_method}'" ); 296 | } 297 | $data = _rserve_make_data(self::DT_STRING, "{$this->username}\n{$this->password}"); 298 | $r = $this->sendCommand(self::CMD_login, $data ); 299 | if( !$r['is_error'] ) { 300 | return true; 301 | } 302 | throw new Exception( "Could not login" ); 303 | } 304 | 305 | /** 306 | * Evaluate a string as an R code and return result 307 | * @param string $string 308 | * @param int $parser 309 | */ 310 | public function evalString($string, $parser = null) { 311 | 312 | $data = _rserve_make_data(self::DT_STRING, $string); 313 | 314 | $r = $this->sendCommand(self::CMD_eval, $data ); 315 | if($this->async) { 316 | return true; 317 | } 318 | if( !$r['is_error'] ) { 319 | return $this->parseResponse($r['contents'], $parser); 320 | } 321 | throw new Exception('unable to evaluate', $r); 322 | } 323 | 324 | /** 325 | * Detach the current session from the current connection. 326 | * Save envirnoment could be attached to another R connection later 327 | * @return array with session_key used to 328 | * @throws Exception 329 | */ 330 | public function detachSession() { 331 | $r = $this->sendCommand(self::CMD_detachSession, null); 332 | if( !$r['is_error'] ) { 333 | $x = $r['contents']; 334 | if( strlen($x) != (32 + 3 * 4) ) { 335 | throw new Exception('Invalid response to detach'); 336 | } 337 | 338 | $port = _rserve_int32($x, 4); 339 | $key = substr($x, 12); 340 | $session = new Session($key, $this->host, $port); 341 | 342 | return $session; 343 | } 344 | throw new Exception('Unable to detach sesssion', $r); 345 | } 346 | 347 | /** 348 | * Assign a value to a symbol in R 349 | * @param string $symbol name of the variable to set (should be compliant with R syntax !) 350 | * @param REXP $value value to set 351 | */ 352 | public function assign($symbol, REXP $value) { 353 | $symbol = (string)$symbol; 354 | $data = _rserve_make_data(self::DT_STRING, $symbol); 355 | $serializer = new Serializer(); 356 | $bin = $serializer->serialize($value); 357 | $data .= _rserve_make_data(self::DT_SEXP, $bin); 358 | $r = $this->sendCommand(self::CMD_assignSEXP, $data); 359 | return $r; 360 | } 361 | 362 | public function setEncoding($encoding) { 363 | $this->sendCommand(self::CMD_setEncoding, _rserve_make_data(self::DT_STRING, $encoding)); 364 | } 365 | 366 | /** 367 | * Get the response from a command 368 | * @param resource $socket 369 | * @return array contents 370 | */ 371 | protected function getResponse() { 372 | $header = null; 373 | $n = socket_recv($this->socket, $header, 16, 0); 374 | if ($n != 16) { 375 | // header should be sent in one block of 16 bytes 376 | return false; 377 | } 378 | $len = _rserve_int32($header, 4); 379 | $ltg = $len; // length to get 380 | $buf = ''; 381 | while ($ltg > 0) { 382 | $n = socket_recv($this->socket, $b2, $ltg, 0); 383 | if ($n > 0) { 384 | $buf .= $b2; 385 | unset($b2); 386 | $ltg -= $n; 387 | } else { 388 | break; 389 | } 390 | } 391 | $res = _rserve_int32($header); 392 | return([ 393 | 'code'=>$res, 394 | 'is_error'=>($res & 15) != 1, 395 | 'error'=>($res >> 24) & 127, 396 | 'header'=>$header, 397 | 'contents'=>$buf 398 | ]); 399 | } 400 | 401 | /** 402 | * Create a new connection to Rserve for async calls 403 | * @return Rserve_Connection 404 | */ 405 | public function newConnection() { 406 | $newConnection = clone($this); 407 | $newConnection->openSocket(); 408 | return $newConnection; 409 | } 410 | 411 | 412 | /** 413 | * Get results from an eval command in async mode 414 | * @param Parser $parser, if null use internal parser 415 | * @return mixed contents of response 416 | */ 417 | public function getResults($parser = null) { 418 | $r = $this->getResponse(); 419 | if( !$r['is_error'] ) { 420 | return $this->parseResponse($r['contents'], $parser); 421 | } 422 | throw new Exception('unable to evaluate', $r); 423 | } 424 | 425 | /** 426 | * Close the current connection 427 | */ 428 | public function close() { 429 | return socket_close($this->socket); 430 | } 431 | 432 | /** 433 | * send a command to Rserve 434 | * @param int $command command code 435 | * @param string $data data packets 436 | * @return int if $async, true 437 | */ 438 | protected function sendCommand($command, $data) { 439 | 440 | $pkt = _rserve_make_packet($command, $data); 441 | 442 | if($this->debug) { 443 | $this->debugPacket($pkt); 444 | } 445 | 446 | socket_send($this->socket, $pkt, strlen($pkt), 0); 447 | 448 | if($this->async) { 449 | return true; 450 | } 451 | // get response 452 | return $this->getResponse(); 453 | } 454 | 455 | /** 456 | * Debug a Rserve packet 457 | * @param array|string $packet 458 | */ 459 | public function debugPacket($packet) { 460 | /* 461 | [0] (int) command 462 | [4] (int) length of the message (bits 0-31) 463 | [8] (int) offset of the data part 464 | [12] (int) length of the message (bits 32-63) 465 | */ 466 | if(is_array($packet)) { 467 | $buf = $packet['contents']; 468 | $header = $packet['header']; 469 | } else { 470 | $header = substr($packet, 0, 16); 471 | $buf = substr($packet, 16); 472 | } 473 | $command = _rserve_int32($header, 0); 474 | $lengthLow = _rserve_int32($header, 4); 475 | $offset = _rserve_int32($header, 8); 476 | $lenghtHigh = _rserve_int32($header, 12); 477 | if($command & self::CMD_Response) { 478 | $is_error = $command & 15 != 1; 479 | $cmd = 'CMD Response'.(($is_error) ? 'OK' : 'Error'); 480 | $err = ($command >> 24) & 0x7F; 481 | } else { 482 | $cmd = dechex($command) & 0xFFF; 483 | } 484 | echo '[header:<'.$cmd.' Length:'.dechex($lenghtHigh).'-'.dechex($lengthLow).' offset'.$offset.">\n"; 485 | $len = strlen($buf); 486 | $i = 0; 487 | while($len > 0) { 488 | $type = _rserve_int8($buf, $i); 489 | $m_len = _rserve_int24($buf, $i+1); 490 | $i += 4; 491 | $i += $m_len; 492 | $len -= $m_len + 4; 493 | echo 'data:<'.$this->getDataTypeTitle($type).' length:'.$m_len.">\n"; 494 | } 495 | echo "]\n"; 496 | } 497 | 498 | /** 499 | * Data Type value to label 500 | * @param int $x 501 | */ 502 | public function getDataTypeTitle($x) { 503 | switch($x) { 504 | case self::DT_INT : 505 | $m = 'int'; 506 | break; 507 | case self::DT_CHAR : 508 | $m = 'char'; 509 | break; 510 | case self::DT_DOUBLE : 511 | $m = 'double'; 512 | break; 513 | case self::DT_STRING : 514 | $m = 'string'; 515 | break; 516 | case self::DT_BYTESTREAM : 517 | $m = 'stream'; 518 | break; 519 | 520 | case self::DT_SEXP : 521 | $m = 'sexp'; 522 | break; 523 | 524 | case self::DT_ARRAY : 525 | $m = 'array'; 526 | break; 527 | default: 528 | $m = 'unknown'; 529 | } 530 | return $m; 531 | } 532 | 533 | /** 534 | * Translate an error code to an error message 535 | * @param int $code 536 | */ 537 | public function getErrorMessage($code) { 538 | switch($code) { 539 | case self::ERR_auth_failed : $m = 'auth failed'; break; 540 | case self::ERR_conn_broken : $m = 'connexion broken'; break; 541 | case self::ERR_inv_cmd : $m = 'invalid command'; break; 542 | case self::ERR_inv_par : $m = 'invalid parameter'; break; 543 | case self::ERR_Rerror : $m = 'R error'; break; 544 | case self::ERR_IOerror : $m = 'IO error'; break; 545 | case self::ERR_not_open : $m = 'not open'; break; 546 | case self::ERR_access_denied : $m = 'access denied'; break; 547 | case self::ERR_unsupported_cmd: $m = 'unsupported command'; break; 548 | case self::ERR_unknown_cmd : $m = 'unknown command'; break; 549 | case self::ERR_data_overflow : $m = 'data overflow'; break; 550 | case self::ERR_object_too_big : $m = 'object too big'; break; 551 | case self::ERR_out_of_mem : $m = 'out of memory' ; break; 552 | case self::ERR_ctrl_closed : $m = 'control closed'; break; 553 | case self::ERR_session_busy : $m = 'session busy'; break; 554 | case self::ERR_detach_failed : $m = 'detach failed'; break; 555 | default: 556 | $m = 'unknown error'; 557 | } 558 | return $m; 559 | } 560 | 561 | } 562 | -------------------------------------------------------------------------------- /src/Evaluator.php: -------------------------------------------------------------------------------- 1 | connexion = new Connection($connexion); 41 | } else { 42 | $this->connexion = $connexion; 43 | } 44 | $this->parser = $this->createParser($parser); 45 | } 46 | 47 | public function createParser($parser) { 48 | if( is_integer($parser) ) { 49 | $this->parserType = $parser; 50 | if($parser == self::PARSER_NATIVE) { 51 | return null; // no need to create parser 52 | } 53 | if($parser == self::PARSER_WRAPPED) { 54 | return new NativeArray(['wrapper'=>true]); 55 | } 56 | if($parser == self::PARSER_REXP) { 57 | return new REXP(); 58 | } 59 | } else { 60 | return $parser; 61 | } 62 | } 63 | 64 | public function decorate($command) { 65 | switch($this->parserType) { 66 | case self:: PARSER_NATIVE: 67 | case self:: PARSER_WRAPPED: 68 | return 'r= try({'.$command.'}, silent=T); if( inherits(r,"try-error")) { r = structure(list("try-error"=1,message=unclass(r)), class="try-error") }; r'; 69 | break; 70 | 71 | case self::PARSER_REXP: 72 | return 'r= try({'.$command.'}, silent=T);'; 73 | break; 74 | } 75 | if(!$this->parserType) { 76 | throw new Exception('Unhandled parser type'); 77 | } 78 | } 79 | 80 | public function evaluate($command) { 81 | return $this->connexion->evalString($this->decorate($command), $this->parser); 82 | } 83 | } -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | packet = $packet; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/Parser.php: -------------------------------------------------------------------------------- 1 | self::XT_HAS_ATTR) { 46 | $ra &= ~ self::XT_HAS_ATTR; 47 | $al = _rserve_int24 ( $r, $i + 1 ); 48 | $tmp = $i; // use temporary to protect current offset 49 | $attr = $this->parse ( $buf, $tmp ); 50 | $result ['attr'] = $attr; 51 | $i += $al + 4; // add attribute length 52 | } 53 | if ($ra == self::XT_NULL) { 54 | return $result; 55 | } 56 | if ($ra == self::XT_VECTOR) { // generic vector 57 | $a = []; 58 | while ( $i < $eoa ) { 59 | $a [] = $this->parse ( $buf, $i ); 60 | } 61 | $result ['contents'] = $a; 62 | } 63 | if ($ra == self::XT_SYMNAME) { // symbol 64 | $oi = $i; 65 | while ( $i < $eoa && ord ( $r [$i] ) != 0 ) { 66 | $i ++; 67 | } 68 | $result ['contents'] = substr ( $buf, $oi, $i - $oi ); 69 | } 70 | if ($ra == self::XT_LIST_NOTAG || $ra == self::XT_LANG_NOTAG) { // pairlist w/o tags 71 | $a = []; 72 | while ( $i < $eoa ) 73 | $a [] = $this->parse ( $buf, $i ); 74 | $result ['contents'] = $a; 75 | } 76 | if ($ra == self::XT_LIST_TAG || $ra == self::XT_LANG_TAG) { // pairlist with tags 77 | $a = []; 78 | while ( $i < $eoa ) { 79 | $val = $this->parse ( $buf, $i ); 80 | $tag = $this->parse( $buf, $i ); 81 | $a [$tag] = $val; 82 | } 83 | $result ['contents'] = $a; 84 | } 85 | if ($ra == self::XT_ARRAY_INT) { // integer array 86 | $a = []; 87 | while ( $i < $eoa ) { 88 | $a [] = _rserve_int32 ( $r, $i ); 89 | $i += 4; 90 | } 91 | if (count ( $a ) == 1) { 92 | $result ['contents'] = $a [0]; 93 | } 94 | $result ['contents'] = $a; 95 | } 96 | if ($ra == self::XT_ARRAY_DOUBLE) { // double array 97 | $a = []; 98 | while ( $i < $eoa ) { 99 | $a [] = _rserve_flt64 ( $r, $i ); 100 | $i += 8; 101 | } 102 | if (count ( $a ) == 1) { 103 | $result ['contents'] = $a [0]; 104 | } 105 | $result ['contents'] = $a; 106 | } 107 | if ($ra == self::XT_ARRAY_STR) { // string array 108 | $a = []; 109 | $oi = $i; 110 | while ( $i < $eoa ) { 111 | if (ord ( $r [$i] ) == 0) { 112 | $a [] = substr ( $r, $oi, $i - $oi ); 113 | $oi = $i + 1; 114 | } 115 | $i ++; 116 | } 117 | if (count ( $a ) == 1) { 118 | $result ['contents'] = $a [0]; 119 | } 120 | $result ['contents'] = $a; 121 | } 122 | if ($ra == self::XT_ARRAY_BOOL) { // boolean vector 123 | $n = _rserve_int32 ( $r, $i ); 124 | $result ['size'] = $n; 125 | $i += 4; 126 | $k = 0; 127 | $a = []; 128 | while ( $k < $n ) { 129 | $v = _rserve_int8 ( $r, $i ++ ); 130 | $a [$k] = ($v === 1) ? true : (($v === 0) ? false : null); 131 | ++ $k; 132 | } 133 | if (count ( $a ) == 1) { 134 | $result ['contents'] = $a [0]; 135 | } 136 | $result ['contents'] = $a; 137 | } 138 | if ($ra == self::XT_RAW) { // raw vector 139 | $len = _rserve_int32 ( $r, $i ); 140 | $i += 4; 141 | $result ['size'] = $len; 142 | $result ['contents'] = substr ( $r, $i, $len ); 143 | } 144 | if ($ra == self::XT_ARRAY_CPLX) { 145 | $real = []; 146 | $im = []; 147 | while ( $i < $eoa ) { 148 | $real [] = _rserve_flt64 ( $r, $i ); 149 | $i += 8; 150 | $im [] = _rserve_flt64 ( $r, $i ); 151 | $i += 8; 152 | } 153 | if (count ( $real ) == 1) { 154 | $a = [$real [0], $im [0]]; 155 | } else { 156 | $a = [$real, $im]; 157 | } 158 | $result ['contents'] = $a; 159 | } 160 | if ($ra == 48) { // unimplemented type in Rserve 161 | $uit = _rserve_int32 ( $r, $i ); 162 | $result ['unknownType'] = $uit; 163 | } 164 | return $result; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Parser/Exception.php: -------------------------------------------------------------------------------- 1 | use_wrapper = isset($options['wrapper']) ? $options['wrapper'] : false; 16 | $this->factor_as_string = isset($options['factor_as_string']) ? (bool)$options['factor_as_string'] : true; 17 | } 18 | 19 | /** 20 | * SEXP to php array parser 21 | * parse SEXP results -- limited implementation for now (large packets and some data types are not supported) 22 | * @param string $buf 23 | * @param int $offset 24 | * @return native php array or a RNative object if if the static property $use_array_object is TRUE 25 | */ 26 | public function parse($buf, &$offset) { 27 | $attr = NULL; 28 | $r = $buf; 29 | $i = $offset; 30 | 31 | // some simple parsing - just skip attributes and assume short responses 32 | $ra = _rserve_int8($r, $i); 33 | $rl = _rserve_int24($r, $i + 1); 34 | $i += 4; 35 | 36 | $offset = $eoa = $i + $rl; 37 | //echo '[ '.self::xtName($ra & 63).', length '.$rl.' ['.$i.' - '.$eoa.']
'; 38 | if (($ra & 64) == 64) { 39 | throw new Exception('long packets are not supported (yet).'); 40 | } 41 | if ($ra > self::XT_HAS_ATTR) { 42 | //echo '(ATTR*['; 43 | $ra &= ~self::XT_HAS_ATTR; 44 | $al = _rserve_int24($r, $i + 1); 45 | $tmp = $i; // use temporary to protect current offset 46 | $attr = $this->parse($buf, $tmp); 47 | //echo '])'; 48 | $i += $al + 4; 49 | } 50 | switch($ra) { 51 | case self::XT_NULL: 52 | $a = null; 53 | break; 54 | case self::XT_VECTOR: // generic vector 55 | $a = []; 56 | while ($i < $eoa) { 57 | $a[] = $this->parse($buf, $i); 58 | } 59 | 60 | // if the 'names' attribute is set, convert the plain array into a map 61 | if ( isset($attr['names']) ) { 62 | $names = $attr['names']; 63 | if(is_string($names)) { 64 | $names = [$names]; 65 | } 66 | $a = array_combine($names, $a); 67 | 68 | } 69 | break; 70 | 71 | case self::XT_INT: 72 | $a = _rserve_int32($r, $i); 73 | $i += 4; 74 | break; 75 | 76 | case self::XT_DOUBLE: 77 | $a = _rserve_flt64($r, $i); 78 | $i += 8; 79 | break; 80 | 81 | case self::XT_BOOL: 82 | $v = _rserve_int8($r, $i++); 83 | $a = ($v == 1) ? true : (($v == 0) ? false : null); 84 | break; 85 | 86 | case self::XT_SYM: 87 | case self::XT_SYMNAME: // symbol 88 | $oi = $i; 89 | while ($i < $eoa && ord($r[$i]) != 0) { 90 | $i++; 91 | } 92 | $a = substr($buf, $oi, $i - $oi); 93 | break; 94 | 95 | case self::XT_LANG_NOTAG: 96 | case self::XT_LIST_NOTAG : // pairlist w/o tags 97 | $a = []; 98 | while ($i < $eoa) { 99 | $a[] = $this->parse($buf, $i); 100 | } 101 | break; 102 | 103 | case self::XT_LIST_TAG: 104 | case self::XT_LANG_TAG: 105 | // pairlist with tags 106 | $a = []; 107 | while ($i < $eoa) { 108 | $val = $this->parse($buf, $i); 109 | $tag = $this->parse($buf, $i); 110 | $a[$tag] = $val; 111 | } 112 | break; 113 | 114 | case self::XT_ARRAY_INT: // integer array 115 | $a = []; 116 | while ($i < $eoa) { 117 | $a[] = _rserve_int32($r, $i); 118 | $i += 4; 119 | } 120 | $n = count($a); 121 | if ($n == 1) { 122 | $a = $a[0]; 123 | } 124 | 125 | // If factor, then transform to characters 126 | if( $this->factor_as_string && isset($attr['class']) ) { 127 | $c = $attr['class']; 128 | $is_factor = is_string($c) && ($c == 'factor'); 129 | if($is_factor) { 130 | $levels = $attr['levels']; 131 | if($n == 1) { 132 | if($a < 0) { 133 | $a = null; 134 | } else { 135 | if (is_array($levels)) { 136 | $a = $levels[ $a ]; 137 | } else { 138 | // Only one levels & current value is first level, ok 139 | if ($a == 1) { 140 | $a = $levels; 141 | } 142 | } 143 | } 144 | } else { 145 | for ($k = 0; $k < $n; ++$k) { 146 | $i = $a[$k]; 147 | if ($i < 0) { 148 | $a[$k] = null; 149 | } else { 150 | $a[$k] = $levels[ $i -1]; 151 | } 152 | } 153 | } 154 | } 155 | } 156 | break; 157 | 158 | case self::XT_ARRAY_DOUBLE:// double array 159 | $a = []; 160 | while ($i < $eoa) { 161 | $a[] = _rserve_flt64($r, $i); 162 | $i += 8; 163 | } 164 | if (count($a) == 1) { 165 | $a = $a[0]; 166 | } 167 | break; 168 | 169 | case self::XT_ARRAY_STR: // string array 170 | $a = []; 171 | $oi = $i; 172 | while ($i < $eoa) { 173 | if (ord($r[$i]) == 0) { 174 | $a[] = substr($r, $oi, $i - $oi); 175 | $oi = $i + 1; 176 | } 177 | $i++; 178 | } 179 | if (count($a) == 1) { 180 | $a = $a[0]; 181 | } 182 | break; 183 | 184 | case self::XT_ARRAY_BOOL: // boolean vector 185 | $n = _rserve_int32($r, $i); 186 | $i += 4; 187 | $k = 0; 188 | $a = []; 189 | while ($k < $n) { 190 | $v = _rserve_int8($r, $i++); 191 | $a[$k++] = ($v == 1) ? true : (($v == 0) ? false : null); 192 | } 193 | if ($n == 1) { 194 | $a = $a[0]; 195 | } 196 | break; 197 | 198 | case self::XT_RAW: // raw vector 199 | $len = _rserve_int32($r, $i); 200 | $i += 4; 201 | $a = substr($r, $i, $len); 202 | break; 203 | 204 | case self::XT_ARRAY_CPLX: 205 | // real part 206 | $real = []; 207 | $im = []; 208 | while ($i < $eoa) { 209 | $real[] = _rserve_flt64($r, $i); 210 | $i += 8; 211 | $im[] = _rserve_flt64($r, $i); 212 | $i += 8; 213 | } 214 | if (count($real) == 1) { 215 | $a = [$real[0], $im[0]]; 216 | } else { 217 | $a = [$real, $im]; 218 | } 219 | break; 220 | 221 | case 48: // unimplemented type in Rserve 222 | $uit = _rserve_int32($r, $i); 223 | // echo "Note: result contains type #$uit unsupported by Rserve.
"; 224 | $a = null; 225 | break; 226 | 227 | default: 228 | echo 'Warning: type '.$ra.' is currently not implemented in the PHP client.'; 229 | $a = null; 230 | } // end switch 231 | 232 | if( $this->use_wrapper ) { 233 | if( is_array($a) && $attr) { 234 | return new ArrayWrapper($a, $attr, $ra); 235 | } else { 236 | return $a; 237 | } 238 | } 239 | return $a; 240 | } 241 | 242 | } -------------------------------------------------------------------------------- /src/Parser/REXP.php: -------------------------------------------------------------------------------- 1 | self::XT_HAS_ATTR) { 43 | $ra &= ~ self::XT_HAS_ATTR; 44 | $al = _rserve_int24( $r, $i + 1 ); 45 | $tmp = $i; 46 | $attr = $this->parse( $buf, $tmp ); 47 | $i += $al + 4; 48 | } 49 | 50 | $class = ($attr) ? $attr->at( 'class' ) : null; 51 | 52 | if ($class) { 53 | $class = $class->getValues(); 54 | } 55 | switch ($ra) { 56 | case self::XT_NULL : 57 | $a = new RNull(); 58 | break; 59 | 60 | case self::XT_VECTOR : // generic vector 61 | $v = []; 62 | while ( $i < $eoa ) { 63 | $v [] = $this->parse( $buf, $i ); 64 | } 65 | $use_df = false; 66 | if ($class) { 67 | if (in_array('data.frame', $class )) { 68 | $use_df = true; 69 | } 70 | } 71 | $a = $use_df ? new Dataframe() : new GenericVector(); 72 | $a->setValues ( $v ); 73 | break; 74 | 75 | case self::XT_SYMNAME : // symbol 76 | $oi = $i; 77 | while ( $i < $eoa && ord ( $r [$i] ) != 0 ) { 78 | $i ++; 79 | } 80 | $v = substr ( $buf, $oi, $i - $oi ); 81 | $a = new Symbol (); 82 | $a->setValue ( $v ); 83 | break; 84 | 85 | case self::XT_LIST_NOTAG : 86 | case self::XT_LANG_NOTAG : // pairlist w/o tags 87 | $v = []; 88 | while ( $i < $eoa ) { 89 | $v [] = $this->parse( $buf, $i ); 90 | } 91 | $a = ($ra == self::XT_LIST_NOTAG) ? new RList() : new Language(); 92 | $a->setValues( $a ); 93 | break; 94 | 95 | case self::XT_LIST_TAG : 96 | case self::XT_LANG_TAG : // pairlist with tags 97 | $v = []; 98 | $names = []; 99 | while ( $i < $eoa ) { 100 | $v[] = $this->parse( $buf, $i ); 101 | $names[] = $this->parse( $buf, $i ); 102 | } 103 | $a = ($ra == self::XT_LIST_TAG) ? new RList() : new Language(); 104 | $a->setValues( $v ); 105 | $a->setNames( $names ); 106 | break; 107 | 108 | case self::XT_ARRAY_INT : // integer array 109 | $v = []; 110 | while ( $i < $eoa ) { 111 | $v [] = _rserve_int32( $r, $i ); 112 | $i += 4; 113 | } 114 | $use_factor = false; 115 | if ($class) { 116 | if (in_array( 'factor', $class )) { 117 | $use_factor = true; 118 | } 119 | } 120 | $a = $use_factor ? new Factor() : new Integer(); 121 | $a->setValues( $v ); 122 | break; 123 | 124 | case self::XT_ARRAY_DOUBLE : // double array 125 | $v = []; 126 | while ( $i < $eoa ) { 127 | $v [] = _rserve_flt64( $r, $i ); 128 | $i += 8; 129 | } 130 | $a = new Double(); 131 | $a->setValues( $v ); 132 | break; 133 | 134 | case self::XT_ARRAY_STR : // string array 135 | $v = []; 136 | $oi = $i; 137 | while ( $i < $eoa ) { 138 | if ( ord( $r[$i] ) == 0) { 139 | $v[] = substr( $r, $oi, $i - $oi ); 140 | $oi = $i + 1; 141 | } 142 | $i++; 143 | } 144 | 145 | if($class && in_array('try-error', $class)) { 146 | $a = new Error(); 147 | } else { 148 | $a = new RString(); 149 | } 150 | $a->setValues( $v ); 151 | break; 152 | 153 | case self::XT_ARRAY_BOOL : // boolean vector 154 | $n = _rserve_int32( $r, $i ); 155 | $i += 4; 156 | $k = 0; 157 | $vv = []; 158 | while ( $k < $n ) { 159 | $v = _rserve_int8( $r, $i ++ ); 160 | $vv[$k] = ($v == 1) ? true : (($v == 0) ? false : null); 161 | $k ++; 162 | } 163 | $a = new Logical(); 164 | $a->setValues( $vv ); 165 | break; 166 | 167 | case self::XT_RAW : // raw vector 168 | $len = _rserve_int32( $r, $i ); 169 | $i += 4; 170 | $v = substr( $r, $i, $len ); 171 | $a = new Raw(); 172 | $a->setValue( $v ); 173 | break; 174 | 175 | case self::XT_ARRAY_CPLX : 176 | $v = []; 177 | while ( $i < $eoa ) { 178 | $real = _rserve_flt64( $r, $i ); 179 | $i += 8; 180 | $im = _rserve_flt64( $r, $i ); 181 | $i += 8; 182 | $v [] = [$real, $im]; 183 | } 184 | $a = new Complex(); 185 | $a->setValues( $v ); 186 | break; 187 | /* 188 | * case 48: // unimplemented type in Rserve 189 | * $uit = _rserve_int32($r, $i); 190 | * // echo "Note: result contains type #$uit unsupported by Rserve.
"; 191 | * $a = null; 192 | * break; 193 | */ 194 | default : 195 | // handle unknown type 196 | $a = new Unknown( $ra ); 197 | } 198 | if ($attr && is_object( $a )) { 199 | $a->setAttributes( $attr ); 200 | } 201 | return $a; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/Protocol.php: -------------------------------------------------------------------------------- 1 | '; 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /src/REXP.php: -------------------------------------------------------------------------------- 1 | attr = $attr; 40 | } 41 | 42 | /** 43 | * Check if an attribute exists for a given name 44 | * @param REXP $name 45 | * @return bool 46 | */ 47 | public function hasAttribute($name):bool { 48 | if( !$this->attr ) { 49 | return false; 50 | } 51 | return true; 52 | } 53 | 54 | /** 55 | * Get an attribute 56 | * @param REXP $name 57 | * @return REXP 58 | */ 59 | public function getAttribute($name) { 60 | if( !$this->attr ) { 61 | return null; 62 | } 63 | return $this->attr->at($name); 64 | } 65 | 66 | /** 67 | * get attributes for this REXP 68 | * @return RList|null 69 | */ 70 | public function attributes() { 71 | return $this->attr; 72 | } 73 | 74 | /** 75 | * Is a vector (list of indexed values whatever it's type) 76 | * @return bool 77 | */ 78 | public function isVector():bool { 79 | return false; 80 | } 81 | 82 | /** 83 | * Is an Integer vector 84 | * @return bool 85 | */ 86 | public function isInteger():bool { 87 | return false; 88 | } 89 | 90 | /** 91 | * Is a numeric vector (Double) 92 | * @return bool 93 | */ 94 | public function isNumeric():bool { 95 | return false; 96 | } 97 | 98 | /** 99 | * Is a logical vector 100 | * @return bool 101 | */ 102 | public function isLogical():bool { 103 | return false; 104 | } 105 | 106 | /** 107 | * Is a string vector 108 | * @return bool 109 | */ 110 | public function isString():bool { 111 | return false; 112 | } 113 | 114 | /** 115 | * Is a symbol vector 116 | * @return bool 117 | */ 118 | public function isSymbol():bool { 119 | return false; 120 | } 121 | 122 | /** 123 | * Is a raw vector (binary) 124 | * @return bool 125 | */ 126 | public function isRaw():bool { 127 | return false; 128 | } 129 | 130 | /** 131 | * Is a list (Rserve_Rexp_List) 132 | * @return bool 133 | */ 134 | public function isList():bool { 135 | return false; 136 | } 137 | 138 | /** 139 | * Is a null value 140 | * @return bool 141 | */ 142 | public function isNull():bool { 143 | return false; 144 | } 145 | 146 | /** 147 | * Is a language expression 148 | * @return bool 149 | */ 150 | public function isLanguage():bool { 151 | return false; 152 | } 153 | 154 | /** 155 | * Is a factor vector 156 | * @return bool 157 | */ 158 | public function isFactor():bool { 159 | return false; 160 | } 161 | 162 | /** 163 | * Is an expression 164 | * @return bool 165 | */ 166 | public function isExpression():bool { 167 | return false; 168 | } 169 | 170 | /** 171 | * object content's length 172 | * @return int 173 | */ 174 | public function length():int { 175 | return 0; 176 | } 177 | 178 | /** 179 | * Return R class 180 | * @return string 181 | */ 182 | public function getClass():string { 183 | $class = $this->getAttribute('class'); 184 | if($class) { 185 | if(!$class instanceof Vector) { 186 | throw new Exception("Class attribute must be a vector"); 187 | } 188 | return $class->getValues(); 189 | } 190 | $type = $this->getType(); 191 | switch($type) { 192 | case Parser::XT_ARRAY_BOOL: 193 | $class = 'logical'; 194 | break; 195 | case Parser::XT_ARRAY_INT: 196 | $class = 'integer'; 197 | break; 198 | case Parser::XT_ARRAY_DOUBLE: 199 | $class = 'numeric'; 200 | break; 201 | case Parser::XT_ARRAY_STR: 202 | $class ='character'; 203 | break; 204 | case Parser::XT_FACTOR: 205 | $class = 'factor'; 206 | break; 207 | default: 208 | $class = 'unknown'; 209 | } 210 | return $class; 211 | } 212 | 213 | /** 214 | * Get an HTML representation of the object 215 | * For debugging purpose 216 | */ 217 | public function toHTML() { 218 | return '

'.Parser::xtName($this->getType()).''.$this->attrToHTML().'
'; 219 | } 220 | 221 | protected function attrToHTML() { 222 | if($this->attr) { 223 | return '
'.$this->attr->toHTML().'
'; 224 | } 225 | } 226 | 227 | /** 228 | * Get R Type (@see Rserve_Parser) 229 | * @return int 230 | */ 231 | public function getType() { 232 | return Parser::XT_VECTOR; 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /src/REXP/Complex.php: -------------------------------------------------------------------------------- 1 | at($index); 15 | if( is_array($v) ) { 16 | return $v[$part]; 17 | } 18 | return null; 19 | } 20 | $r = []; 21 | foreach($this->values as $v) { 22 | $r[] = $v[$part]; 23 | } 24 | return $r; 25 | } 26 | 27 | /** 28 | * Get imaginary part of vector 29 | * @param int index of vector 30 | * @return float 31 | */ 32 | public function getImaginary($index = null) { 33 | return $this->getCplx($index, 1); 34 | } 35 | 36 | /** 37 | * Get real part of vector 38 | * @param int index of vector 39 | * @return float 40 | */ 41 | public function getReal($index = null) { 42 | return $this->getCplx($index, 0); 43 | } 44 | 45 | /** 46 | * (non-PHPdoc) 47 | * @see Rserve_REXP_Vector::valueToHTML() 48 | */ 49 | protected function valueToHTML($v) { 50 | return $v[0]+' + '.$v[1].'i'; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/REXP/Dataframe.php: -------------------------------------------------------------------------------- 1 | columns names 11 | * @return array() 12 | */ 13 | public function getNames() { 14 | /** 15 | * @var Vector 16 | */ 17 | $n = $this->getAttribute('names'); 18 | if($n) { 19 | return $n->getValues(); 20 | } 21 | return NULL; 22 | } 23 | 24 | /** 25 | * R rownames() 26 | * @return array() 27 | */ 28 | public function getRowNames() { 29 | /** 30 | * @var Vector 31 | */ 32 | $n = $this->getAttribute('row.names'); 33 | if($n) { 34 | return $n->getValues(); 35 | } 36 | return NULL; 37 | } 38 | 39 | /** 40 | * Number of rows 41 | * @return int 42 | */ 43 | public function nrow() { 44 | $v = $this->getValues(); 45 | if( is_array($v) ) { 46 | $v = $v[0]; 47 | return $v->length(); 48 | } 49 | return 0; 50 | } 51 | 52 | /** 53 | * Number of columns 54 | * @return int 55 | */ 56 | public function ncol() { 57 | return $this->length(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/REXP/Double.php: -------------------------------------------------------------------------------- 1 | levels; 33 | } 34 | 35 | /** 36 | * Set levels from 37 | */ 38 | public function setLevels($levels) { 39 | if($levels instanceof RString) { 40 | $levels = $levels->getValues(); 41 | } 42 | $this->levels = $levels; 43 | } 44 | 45 | /** 46 | * Convert an levels encoded vector to a character vector 47 | * @return REXP 48 | */ 49 | public function asCharacter(): REXP { 50 | $levels = $this->levels; 51 | $r = []; 52 | foreach($this->values as $v) { 53 | $r[] = $v >= 0 ? $levels[$v] : null; 54 | } 55 | $rexp = new RString(); 56 | $rexp->setValues($r); 57 | return $rexp; 58 | } 59 | 60 | public function getType() { 61 | return Parser::XT_FACTOR; 62 | } 63 | 64 | public function setAttributes(RList $attr) { 65 | parent::setAttributes($attr); 66 | /** 67 | * @var Vector|null 68 | */ 69 | $lev = $this->getAttribute('levels'); 70 | if( $lev ) { 71 | $lev = $lev->getValues(); 72 | $levels = []; 73 | $i = 0; 74 | foreach($lev as $l) { 75 | ++$i; 76 | $levels[$i] =(string)$l; // force string for convenience 77 | } 78 | $this->levels = $levels; 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/REXP/GenericVector.php: -------------------------------------------------------------------------------- 1 | setNames($names); 33 | } 34 | } 35 | 36 | /** 37 | * Set names 38 | * @param array $names 39 | */ 40 | public function setNames(array $names) { 41 | if(count($this->values) != count($names)) { 42 | throw new \LengthException('Invalid names length'); 43 | } 44 | $nn = []; 45 | foreach($names as $n) { 46 | $nn[] = (string)$n; 47 | } 48 | $this->names = $nn; 49 | $this->is_named = true; 50 | } 51 | 52 | /** 53 | * return list of names 54 | * @return array 55 | */ 56 | public function getNames() { 57 | return ($this->is_named) ? $this->names : []; 58 | } 59 | 60 | /** 61 | * return true if the list is named 62 | * @return bool 63 | */ 64 | public function isNamed():bool { 65 | return $this->is_named; 66 | } 67 | 68 | /** 69 | * Get the value for a given name entry, if list is not named, get the indexed element 70 | * @param string $name 71 | * @return REXP|mixed 72 | */ 73 | public function at($name) { 74 | if( $this->is_named ) { 75 | $i = array_search($name, $this->names); 76 | if($i < 0) { 77 | return null; 78 | } 79 | return $this->values[$i]; 80 | } 81 | } 82 | 83 | /** 84 | * Return element at the index $i 85 | * @param int $i 86 | * @return mixed Rserve_REXP or native value 87 | */ 88 | public function atIndex($i) { 89 | $i = (int)$i; 90 | $n = count($this->values); 91 | if( ($i < 0) || ($i >= $n) ) { 92 | throw new \OutOfBoundsException('Invalid index'); 93 | } 94 | return $this->values[$i]; 95 | } 96 | 97 | public function isList():bool { 98 | return true; 99 | } 100 | 101 | public function offsetExists($offset):bool { 102 | if($this->is_named) { 103 | return array_search($offset, $this->names) >= 0; 104 | } else { 105 | return isset($this->names[$offset]); 106 | } 107 | } 108 | 109 | public function offsetGet($offset) { 110 | return $this->at($offset); 111 | } 112 | 113 | public function offsetSet($offset, $value):void { 114 | throw new Exception('assign not implemented'); 115 | } 116 | 117 | public function offsetUnset($offset):void { 118 | throw new Exception('unset not implemented'); 119 | } 120 | 121 | public function getType() { 122 | if( $this->isNamed() ) { 123 | return Parser::XT_LIST_TAG; 124 | } else { 125 | return Parser::XT_LIST_NOTAG; 126 | } 127 | } 128 | 129 | public function toHTML() { 130 | $is_named = $this->is_named; 131 | $s = '
'; 132 | $n = $this->length(); 133 | $s .= ''; 148 | $s .= $this->attrToHTML(); 149 | $s .= '
'; 150 | return $s; 151 | } 152 | 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/REXP/RNull.php: -------------------------------------------------------------------------------- 1 | '; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/REXP/RString.php: -------------------------------------------------------------------------------- 1 | value); 28 | } 29 | 30 | public function setValue($value) { 31 | $this->value = $value; 32 | } 33 | 34 | public function getValue($value) { 35 | return $this->value; 36 | } 37 | 38 | public function isRaw():bool { 39 | return true; 40 | } 41 | 42 | public function getType() { 43 | return Parser::XT_RAW; 44 | } 45 | 46 | public function toHTML() { 47 | $s = strlen($this->value) > 60 ? substr($this->value,0,60).' (truncated)': $this->value; 48 | return '
raw
'.$s.'
'.$this->attrToHTML().'
'; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/REXP/Symbol.php: -------------------------------------------------------------------------------- 1 | name = $value; 24 | } 25 | 26 | public function getValue() { 27 | return $this->name; 28 | } 29 | 30 | public function isSymbol():bool { 31 | return true; 32 | } 33 | 34 | public function getType() { 35 | return Parser::XT_SYM; 36 | } 37 | 38 | public function toHTML() { 39 | return '
'.Parser::xtName($this->getType()).''.$this->name.$this->attrToHTML().'
'; 40 | } 41 | 42 | public function __toString() { 43 | return $this->name; 44 | } 45 | 46 | public function length():int { 47 | return 1; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/REXP/Unknown.php: -------------------------------------------------------------------------------- 1 | unknowntype = $type; 23 | } 24 | 25 | public function getUnknownType() { 26 | return $this->unknowntype; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/REXP/Vector.php: -------------------------------------------------------------------------------- 1 | values = []; 25 | } 26 | 27 | /** 28 | * return int 29 | */ 30 | public function length():int { 31 | return( count($this->values) ); 32 | } 33 | 34 | public function isVector():bool { 35 | return true; 36 | } 37 | 38 | public function setValues($values) { 39 | $this->values = $values; 40 | } 41 | 42 | public function getValues() { 43 | return $this->values; 44 | } 45 | 46 | /** 47 | * Return dimensions length of the vector 48 | * uses 'dim' attribute if exists or the length of the vector (one dimension vector) 49 | */ 50 | public function dim() { 51 | /** 52 | * @var Vector 53 | */ 54 | $dim = $this->getAttribute('dim'); 55 | if( $dim ) { 56 | return $dim->getValues(); 57 | } 58 | return [$this->length()]; 59 | } 60 | 61 | /** 62 | * Matrix is a multidimensionnal vector 63 | */ 64 | public function isMatrix() { 65 | $dim = $this->dim(); 66 | return count($dim) > 1; 67 | } 68 | 69 | /** 70 | * Get value 71 | * @param unknown_type $index 72 | */ 73 | public function at($index) { 74 | return $this->values[$index] ?? null; 75 | } 76 | 77 | public function getType() { 78 | return Parser::XT_VECTOR; 79 | } 80 | 81 | public function toHTML() { 82 | $s = '
'; 83 | $dim = $this->dim(); 84 | $n = $this->length(); 85 | $s .= ''.Parser::xtName($this->getType()).''; 86 | $s .= '['; 87 | $s .= join(',', $dim); 88 | $s .= ']'; 89 | $s .= '
'; 90 | if($n) { 91 | $m = ($n > 20) ? 20 : $n; 92 | for($i = 0; $i < $m; ++$i) { 93 | $v = $this->values[$i]; 94 | if(is_object($v) AND ($v instanceof REXP)) { 95 | $v = $v->toHTML(); 96 | } else { 97 | $v = $this->valueToHTML($v); 98 | } 99 | $s .= '
'.$v.'
'; 100 | } 101 | } 102 | $s .= '
'; 103 | $s .= $this->attrToHTML(); 104 | $s .= '
'; 105 | return $s; 106 | } 107 | 108 | /** 109 | * HTML representation for a single value of the vector 110 | * @param mixed $v 111 | */ 112 | protected function valueToHTML($v) { 113 | return (string)$v; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Serializer.php: -------------------------------------------------------------------------------- 1 | getType(); 26 | switch($type) { 27 | case self::XT_S4: 28 | case self::XT_NULL: 29 | break; 30 | case self::XT_INT: 31 | $v = (int)$value->at(0); 32 | $contents .= _rserve_mkint32($v); 33 | $o += 4; 34 | break; 35 | case self::XT_DOUBLE: 36 | $v = (float)$value->at(0); 37 | $contents .= _rserve_mkfloat64($v); 38 | $o += 8; 39 | break; 40 | case self::XT_ARRAY_INT: 41 | $vv = $value->getValues(); 42 | $n = count($vv); 43 | for($i = 0; $i < $n; ++$i) { 44 | $v = $vv[$i]; 45 | $contents .= _rserve_mkint32($v); 46 | $o += 4; 47 | } 48 | break; 49 | case self::XT_ARRAY_BOOL: 50 | $vv = $value->getValues(); 51 | $n = count($vv); 52 | $contents .= _rserve_mkint32($n); 53 | $o += 4; 54 | if( $n ) { 55 | for($i = 0; $i < $n; ++$i) { 56 | $v = $vv[$i]; 57 | if(is_null($v)) { 58 | $v = 2; 59 | } else { 60 | $v = (int)$v; 61 | } 62 | if($v != 0 AND $v != 1) { 63 | $v = 2; 64 | } 65 | $contents .= chr($v); 66 | ++$o; 67 | } 68 | while( ($o & 3) != 0 ) { 69 | $contents .= chr(3); 70 | ++$o; 71 | } 72 | } 73 | break; 74 | case self::XT_ARRAY_DOUBLE: 75 | $vv = $value->getValues(); 76 | $n = count($vv); 77 | for($i = 0; $i < $n; ++$i) { 78 | $v = (float)$vv[$i]; 79 | $contents .= _rserve_mkfloat64($v); 80 | $o += 8; 81 | } 82 | break; 83 | case self::XT_RAW : 84 | $v = $value->getValue(); 85 | $n = $value->length(); 86 | $contents .= _rserve_mkint32($n); 87 | $o += 4; 88 | $contents .= $v; 89 | break; 90 | 91 | case self::XT_ARRAY_STR: 92 | $vv = $value->getValues(); 93 | $n = count($vv); 94 | for($i = 0; $i < $n; ++$i) { 95 | $v = $vv[$i]; 96 | if( !is_null($v) ) { 97 | $contents .= $v; 98 | $contents .= chr(0); 99 | $o += strlen($v) + 1; 100 | } else { 101 | $contents .= chr(255).chr(0); 102 | $o += 2; 103 | } 104 | } 105 | while( ($o & 3) != 0) { 106 | $contents .= chr(1); 107 | ++$o; 108 | } 109 | break; 110 | case self::XT_LIST_TAG: 111 | case self::XT_LIST_NOTAG: 112 | case self::XT_LANG_TAG: 113 | case self::XT_LANG_NOTAG: 114 | case self::XT_LIST: 115 | case self::XT_VECTOR: 116 | case self::XT_VECTOR_EXP: 117 | $l = $value->getValues(); 118 | if($type == XT_LIST_TAG || $type == XT_LANG_TAG) { 119 | $names = $value->getNames(); 120 | } 121 | $i = 0; 122 | $n = count($l); 123 | while($i < $n) { 124 | $x = $l[$i]; 125 | if( is_null($x) ) { 126 | $x = new RNull(); 127 | } 128 | $iof = strlen($contents); 129 | $contents .= self::createBinary($x); 130 | if($type == XT_LIST_TAG || $type == XT_LANG_TAG) { 131 | $sym = new Symbol(); 132 | $sym->setValue($names[$i]); 133 | $contents .= $this->serialize($sym); 134 | } 135 | ++$i; 136 | } 137 | break; 138 | 139 | case self::XT_SYMNAME: 140 | case self::XT_STR: 141 | $s = (string)$value->getValues(); 142 | $contents .= $s; 143 | $o += strlen($s); 144 | $contents .= chr(0); 145 | ++$o; 146 | //padding if necessary 147 | while( ($o & 3) != 0) { 148 | $contents .= chr(0); 149 | ++$o; 150 | } 151 | break; 152 | } 153 | /* 154 | TODO: handling attr 155 | $attr = $value->attributes(); 156 | $attr_bin = ''; 157 | if( is_null($attr) ) { 158 | $attr_off = $this->serialize($attr, $attr_bin, 0); 159 | $attr_flag = self::XT_HAS_ATTR; 160 | } else { 161 | $attr_off = 0; 162 | $attr_flag = 0; 163 | } 164 | // [0] (4) header SEXP: len=4+m+n, XT_HAS_ATTR is set 165 | // [4] (4) header attribute SEXP: len=n 166 | // [8] (n) data attribute SEXP 167 | // [8+n] (m) data SEXP 168 | */ 169 | $attr_flag = 0; 170 | $length = $o; 171 | $isLarge = ($length > 0xfffff0); 172 | $code = $type | $attr_flag; 173 | 174 | // SEXP Header (without ATTR) 175 | // [0] (byte) eXpression Type 176 | // [1] (24-bit int) length 177 | $r = chr( $code & 255); 178 | $r .= _rserve_mkint24($length); 179 | $r .= $contents; 180 | return $r; 181 | } 182 | 183 | 184 | } -------------------------------------------------------------------------------- /src/Session.php: -------------------------------------------------------------------------------- 1 | key = $key; 28 | $this->port = $port; 29 | $this->host = $host; 30 | } 31 | 32 | public function __toString() { 33 | $k = base64_encode($this->key); 34 | return sprintf('Session %s:%d identified by base64:%s', $this->host, $this->port, $k); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | >= 8; 58 | $r .= chr($i & 255); 59 | $i >>=8; 60 | $r .= chr($i & 255); 61 | $i >>=8; 62 | $r .= chr($i & 255); 63 | return $r; 64 | } 65 | 66 | /* 67 | * Create a 24 bit integer 68 | * @return string binary representation of the int using 24 bits 69 | */ 70 | function _rserve_mkint24($i) { 71 | $r = chr($i & 255); 72 | $i >>= 8; 73 | $r .= chr($i & 255); 74 | $i >>=8; 75 | $r .= chr($i & 255); 76 | return $r; 77 | } 78 | 79 | /** 80 | * Create a binary representation of float to 64bits 81 | * TODO: works only for intel endianess, should be adapted for no big endian proc 82 | * @param double $v 83 | */ 84 | function _rserve_mkfloat64($v) { 85 | return pack('d', $v); 86 | } 87 | 88 | /** 89 | * 64bit integer to Float 90 | * @param $buf 91 | * @param $o 92 | */ 93 | function _rserve_flt64($buf, $o = 0) { 94 | $ss = substr($buf, $o, 8); 95 | if (Connection::$machine_is_bigendian) { 96 | for ($k = 0; $k < 7; $k++) { 97 | $ss[7 - $k] = $buf[$o + $k]; 98 | } 99 | } 100 | $r = unpack('d', substr($buf, $o, 8)); 101 | return $r[1]; 102 | } 103 | 104 | /** 105 | * Create a packet for QAP1 message 106 | * @param int $cmd command identifier 107 | * @param string $string contents of the message 108 | */ 109 | function _rserve_make_packet($cmd, $data) { 110 | // [0] (int) command 111 | // [4] (int) length of the message (bits 0-31) 112 | // [8] (int) offset of the data part 113 | // [12] (int) length of the message (bits 32-63) 114 | return _rserve_mkint32($cmd) . _rserve_mkint32(strlen($data)) . _rserve_mkint32(0) . _rserve_mkint32(0).$data; 115 | } 116 | 117 | /** 118 | * Make a data packet 119 | * @param unknown_type $type 120 | * @param unknown_type $string NULL terminated string 121 | */ 122 | function _rserve_make_data($type, $string) { 123 | if($type == Connection::DT_STRING) { 124 | $string .= chr(0); 125 | } 126 | $len = strlen($string); // Length of the binary string 127 | $pad = ($len % 4); // Number of padding needed 128 | if($pad > 0) { 129 | $pad = 4 - $pad; 130 | } 131 | $len += $pad; 132 | $s = chr($type & 255); // [0] Type 133 | $s .= _rserve_mkint24($len); // [1] Length (24bits) 134 | $s .= $string; // Data 135 | if($pad) { 136 | $s .= str_repeat(chr(0), $pad); 137 | } 138 | return $s; 139 | } 140 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /config.php 2 | -------------------------------------------------------------------------------- /tests/BaseTest.php: -------------------------------------------------------------------------------- 1 | connectionManager = new ConnectionManager(); 21 | } 22 | 23 | protected function getConnection($withAuth=false): ?Connection { 24 | return $this->connectionManager->create($withAuth); 25 | } 26 | 27 | /** 28 | * Create a reasonably random string (only for testing, no need for secure crypto) 29 | * 30 | * @return string 31 | */ 32 | protected function getRandomString():string { 33 | // random id 34 | $random = ''; 35 | for ($i = 0; $i < 10; ++$i) { 36 | $random .= dechex(mt_rand()); 37 | } 38 | return uniqid($random, true); 39 | } 40 | } -------------------------------------------------------------------------------- /tests/ConnectionManager.php: -------------------------------------------------------------------------------- 1 | host = $this->getvar('RSERVE_HOST'); 21 | 22 | $port = $this->getvar('RSERVE_PORT'); 23 | if($port) { 24 | if($port == '0' || $port == 'unix' || $port == 'socket') { 25 | $this->port = 0; 26 | } 27 | if($port == '') { 28 | $this->port = Connection::DEFAULT_PORT; 29 | } else { 30 | $this->port = (int)$port; 31 | } 32 | } 33 | $this->username = $this->getvar('RSERVE_USER'); 34 | $this->password = $this->getvar('RSERVE_PASS'); 35 | } 36 | 37 | protected function getvar(string $name):?string { 38 | if(defined($name)) { 39 | return constant($name); 40 | } 41 | $value = getenv($name); 42 | return $value !== false ? $value : null; 43 | } 44 | 45 | public function create(bool $requireAuth=false): ?Connection { 46 | if(!$this->host) { 47 | return null; 48 | } 49 | 50 | if(!$this->username && $requireAuth) { 51 | return null; 52 | } 53 | $params = []; 54 | if($this->username) { 55 | $params['username'] = $this->username; 56 | $params['password'] = $this->password; 57 | } 58 | return new Connection($this->host, $this->port, $params); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /tests/Definition.php: -------------------------------------------------------------------------------- 1 | 1, 'titi'=>2]], 39 | // pairlist 40 | ['list("toto"=1,"titi"=2, "tutu"="TOTO")', null, ['toto'=>1, 'titi'=>2, 'tutu'=>'TOTO']], 41 | // factors 42 | ['factor(c("toto","titi","toto","tutu"))', null, ["toto", "titi", "toto", "tutu"]], 43 | // data.frame : Caution with data.frame, use stringsAsFactors=F 44 | ['data.frame("toto"=c(1,2,3),"titi"=c(2,2,3),"tutu"=c("foo","bar","i need some sleep"), stringsAsFactors =F)', null, ['toto'=>[1, 2, 3], 'titi'=>[2, 2, 3], 'tutu'=>['foo', 'bar', 'i need some sleep']]], 45 | ['chisq.test(as.matrix(c(12,58,79,52),ncol=2))[c("statistic","p.value","expected")]', null, ['statistic'=>46.8209, 'p.value'=>3.794258e-10, 'expected'=>[50.25, 50.25, 50.25, 50.25]], ['statistic'=>'round|4', 'p.value'=>'round|16']], 46 | ]; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /tests/LoginTest.php: -------------------------------------------------------------------------------- 1 | getConnection(true); 20 | 21 | if(!$cnx) { 22 | $this->markTestSkipped('skipping authenticated connection aware tests'); 23 | return; 24 | } 25 | 26 | $random_id = $this->getRandomString(); 27 | 28 | $r = $cnx->evalString('x="' . $random_id . '"'); 29 | 30 | $this->assertEquals($r, $random_id); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/ParserNativeTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 31 | if (!$cnx) { 32 | $this->markTestSkipped('skipping connection aware tests'); 33 | return; 34 | } 35 | 36 | $r = $cnx->evalString($cmd); 37 | if (is_array($expected)) { 38 | $this->assertIsArray($r); 39 | if (is_array($r) && !is_null($type)) { 40 | foreach ($r as $x) { 41 | $this->assertIsType($type, $x); 42 | } 43 | } 44 | } else { 45 | if (!is_null($type)) { 46 | $this->assertIsType($type, $r); 47 | } 48 | } 49 | if (!is_null($filters)) { 50 | foreach ($filters as $key => $filter) { 51 | if (is_string($filter)) { 52 | $filter = explode('|', $filter); 53 | } 54 | $f = array_shift($filter); 55 | if (!is_callable($f)) { 56 | throw new Exception('Bad filter ' . $f . ' for ' . $key); 57 | } 58 | $params = array_merge([$r[$key]], $filter); 59 | $r[$key] = call_user_func_array($f, $params); 60 | } 61 | } 62 | $this->assertEquals($r, $expected); 63 | } 64 | 65 | protected function assertIsType($type, $value) 66 | { 67 | switch ($type) { 68 | case Definition::TYPE_BOOL: 69 | $this->assertIsBool($value); 70 | break; 71 | case Definition::TYPE_INT: 72 | $this->assertIsInt($value); 73 | break; 74 | case Definition::TYPE_FLOAT: 75 | $this->assertIsFloat($value); 76 | break; 77 | 78 | case Definition::TYPE_STRING: 79 | $this->assertIsString($value); 80 | break; 81 | 82 | default: 83 | throw new Exception("Unknown type '$type'"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/REXPTest.php: -------------------------------------------------------------------------------- 1 | setValues($values, true); 24 | } else { 25 | $r->setValues($values); 26 | } 27 | } else { 28 | $r->setValue($values); 29 | } 30 | return $r; 31 | } 32 | 33 | public function providerTestParser() 34 | { 35 | return [ 36 | ['Integer', [1, 3, 7, 1129, 231923, 22]], 37 | ['Double', [1.234, 3.432, 4.283, M_PI]], 38 | ['Logical', [true, false, true, true, false, null]], 39 | ['RString', ['toto', 'Lorem ipsum dolor sit amet', '']] 40 | ]; 41 | } 42 | 43 | 44 | /** 45 | * @dataProvider providerTestParser 46 | * @param string $type 47 | * @param mixed $values 48 | */ 49 | public function testParser($type, $values) 50 | { 51 | 52 | $serializer = new Serializer(); 53 | 54 | 55 | $rexp = $this->create_REXP($values, $type); 56 | 57 | $bin = $serializer->serialize($rexp); 58 | 59 | $i = 0; // No offset 60 | 61 | $parser = new Parser_REXP(); 62 | $r2 = $parser->parse($bin, $i); 63 | 64 | $this->assertEquals(get_class($rexp), get_class($r2)); 65 | 66 | $this->assertEquals($rexp->getValues(), $r2->getValues()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/SessionTest.php: -------------------------------------------------------------------------------- 1 | getConnection(); 14 | if(!$cnx) { 15 | $this->markTestSkipped('skipping connection aware tests'); 16 | return; 17 | } 18 | 19 | $random_id = $this->getRandomString(); 20 | 21 | $r = $cnx->evalString('x="' . $random_id . '"'); 22 | 23 | $this->assertEquals($r, $random_id); 24 | 25 | $session = $cnx->detachSession(); 26 | 27 | $cnx = new Connection($session); 28 | 29 | $x = $cnx->evalString('x'); 30 | 31 | $this->assertEquals($x, $random_id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |