diff --git a/.gitignore b/.gitignore index 48b8bf9..51356db 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor/ +dev_rfc.trc diff --git a/.travis.yml b/.travis.yml index 9f607fb..638baf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: php -sudo: false +os: linux +dist: trusty php: - 5.5 env: diff --git a/README.md b/README.md index aed9ca6..04209ae 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,42 @@ This repository implements the [PHP/SAP][phpsap] interface for [Eduard Kouckys l ## Usage ```sh -composer require php-sap/saprfc-koucky:^1.0 +composer require php-sap/saprfc-koucky ``` ```php 'sap.example.com', - 'sysnr' => '001', - 'client' => '002', - 'user' => 'username', - 'passwd' => 'password' -]))) - ->prepareFunction('MY_COOL_SAP_REMOTE_FUNCTION') - ->invoke(['INPUT_PARAM' => 'value']); +//Include the composer autoloader ... +require_once 'vendor/autoload.php'; +//... and add the namespaces of the classes used. +use phpsap\classes\Config\ConfigTypeA; +use phpsap\DateTime\SapDateTime; +use phpsap\saprfc\SapRfc; +/** + * Create an instance of the SAP remote function using its + * name, input parameters, and connection configuration. + * + * The imaginary SAP remote function requires a + * date as input and will return a date as output. + * + * In this case the configuration array is defined manually. + */ +$result = (new SapRfc( + 'MY_COOL_SAP_REMOTE_FUNCTION', + [ + 'IV_DATE' => (new DateTime('2019-12-31')) + ->format(SapDateTime::SAP_DATE) + ], + new ConfigTypeA([ + ConfigTypeA::JSON_ASHOST => 'sap.example.com', + ConfigTypeA::JSON_SYSNR => '999', + ConfigTypeA::JSON_CLIENT => '001', + ConfigTypeA::JSON_USER => 'username', + ConfigTypeA::JSON_PASSWD => 'password' + ]) +))->invoke(); +//The output array contains a DateTime object. +echo $result['OV_DATE']->format('Y-m-d') . PHP_EOL; ``` For further documentation, please read the documentation on [PHP/SAP][phpsap]! diff --git a/composer.json b/composer.json index f249f33..4d55440 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "koucky" ], "provide": { - "php-sap/interfaces": "~1.0.0" + "php-sap/interfaces": "^2.0" }, "conflict": { "php-sap/saprfc-harding": "*", @@ -30,15 +30,15 @@ }, "minimum-stability": "stable", "require": { - "php": "~5.5.0", + "php": "^5.5", "ext-saprfc": "*", - "php-sap/interfaces": "^1.0", - "php-sap/common": "^2.0" + "php-sap/interfaces": "^2.0", + "php-sap/common": "^3.0" }, "require-dev": { "ext-json": "*", "phpunit/phpunit": "^4.8", - "php-sap/integration-tests": "^1.0" + "php-sap/integration-tests": "^3.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 31e4f4f..23b77b3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,31 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "57dc5597b8edc85749479e59c8e04e57", + "content-hash": "d033613aa7710df557f0e60974eeaad2", "packages": [ { "name": "php-sap/common", - "version": "v2.1.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/php-sap/common.git", - "reference": "b8c44683143fd4d122ef6928a49304b7d692f431" + "reference": "d665349aff1ebbdd4ca6e72023fcc3cf676e63e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-sap/common/zipball/b8c44683143fd4d122ef6928a49304b7d692f431", - "reference": "b8c44683143fd4d122ef6928a49304b7d692f431", + "url": "https://api.github.com/repos/php-sap/common/zipball/d665349aff1ebbdd4ca6e72023fcc3cf676e63e3", + "reference": "d665349aff1ebbdd4ca6e72023fcc3cf676e63e3", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=5.5.0", - "php-sap/interfaces": "^1.0", - "psr/container": "^1.0" + "php": "^5.5|^7.0", + "php-sap/datetime": "^1.1", + "php-sap/interfaces": "^2.0" }, "require-dev": { "phpunit/phpunit": "^4.8" }, - "suggest": { - "php-sap/datetime": "Cast and/or export SAP week date, time and timestamp formats to/from DateTime." - }, "type": "library", "autoload": { "psr-4": { @@ -56,32 +53,32 @@ "PHPSAP", "php-sap" ], - "time": "2019-01-23T11:06:58+00:00" + "time": "2020-02-03T13:09:02+00:00" }, { - "name": "php-sap/interfaces", - "version": "v1.0.0", + "name": "php-sap/datetime", + "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/php-sap/interfaces.git", - "reference": "4df96cbf6eb792463641ab55611173e7e6fd2761" + "url": "https://github.com/php-sap/datetime.git", + "reference": "caf2c0df6b6909d7d107388a501961aecee1ce43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-sap/interfaces/zipball/4df96cbf6eb792463641ab55611173e7e6fd2761", - "reference": "4df96cbf6eb792463641ab55611173e7e6fd2761", + "url": "https://api.github.com/repos/php-sap/datetime/zipball/caf2c0df6b6909d7d107388a501961aecee1ce43", + "reference": "caf2c0df6b6909d7d107388a501961aecee1ce43", "shasum": "" }, "require": { "php": ">=5.5.0" }, - "conflict": { - "kba-team/php-sap": "*" + "require-dev": { + "phpunit/phpunit": "^4.8" }, "type": "library", "autoload": { "psr-4": { - "phpsap\\interfaces\\": "src/" + "phpsap\\DateTime\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -95,41 +92,44 @@ "role": "developer" } ], - "description": "Interfaces for implementing the PHP/SAP API. See https://php-sap.github.io for details.", - "homepage": "https://php-sap.github.io", + "description": "Extends PHP's DateTime class by SAP week, date, time and timestamp format.", "keywords": [ "PHPSAP", - "interfaces", - "php-sap" - ], - "time": "2019-01-16T08:33:23+00:00" + "date", + "datetime", + "php-sap", + "sap", + "time", + "timestamp", + "week" + ], + "time": "2019-01-21T13:28:53+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "php-sap/interfaces", + "version": "v2.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/php-sap/interfaces.git", + "reference": "11f476ce12f8fc359c148950b218d186b5c29ec2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-sap/interfaces/zipball/11f476ce12f8fc359c148950b218d186b5c29ec2", + "reference": "11f476ce12f8fc359c148950b218d186b5c29ec2", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-json": "*", + "php": "^5.5|^7.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "conflict": { + "kba-team/php-sap": "*" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "phpsap\\interfaces\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -138,20 +138,19 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Gregor J.", + "email": "gregor-j@users.noreply.github.com", + "role": "developer" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Interfaces for implementing the PHP/SAP API. See https://php-sap.github.io for details.", + "homepage": "https://php-sap.github.io", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "PHPSAP", + "interfaces", + "php-sap" ], - "time": "2017-02-14T16:28:37+00:00" + "time": "2020-02-03T12:48:15+00:00" } ], "packages-dev": [ @@ -211,20 +210,20 @@ }, { "name": "kba-team/memory-container", - "version": "v1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/the-kbA-team/memory-container.git", - "reference": "38bfdbeb64d3e4cc2c0f21bc7c09e325e50f8b87" + "reference": "632355a5d69872a08022ee9b9f7fce93452a2d36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/the-kbA-team/memory-container/zipball/38bfdbeb64d3e4cc2c0f21bc7c09e325e50f8b87", - "reference": "38bfdbeb64d3e4cc2c0f21bc7c09e325e50f8b87", + "url": "https://api.github.com/repos/the-kbA-team/memory-container/zipball/632355a5d69872a08022ee9b9f7fce93452a2d36", + "reference": "632355a5d69872a08022ee9b9f7fce93452a2d36", "shasum": "" }, "require": { - "php": "^5.5|^7.0", + "php": "^5.5", "psr/container": "^1.0" }, "provide": { @@ -255,28 +254,29 @@ "in-memory", "singleton" ], - "time": "2019-01-11T11:24:37+00:00" + "time": "2020-01-27T12:19:33+00:00" }, { "name": "php-sap/integration-tests", - "version": "v1.0.4", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/php-sap/integration-tests.git", - "reference": "9b2e64148f234cf877c4f630bb4905ccc14cb602" + "reference": "13ef752ddd31c150a4c6643d939329ada5301a56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-sap/integration-tests/zipball/9b2e64148f234cf877c4f630bb4905ccc14cb602", - "reference": "9b2e64148f234cf877c4f630bb4905ccc14cb602", + "url": "https://api.github.com/repos/php-sap/integration-tests/zipball/13ef752ddd31c150a4c6643d939329ada5301a56", + "reference": "13ef752ddd31c150a4c6643d939329ada5301a56", "shasum": "" }, "require": { "ext-json": "*", "kba-team/memory-container": "^1.0", "php": "^5.5", - "php-sap/common": "^2.0", - "php-sap/interfaces": "^1.0", + "php-sap/common": "^3.0", + "php-sap/datetime": "^1.1", + "php-sap/interfaces": "^2.0", "phpunit/phpunit": "^4.8" }, "type": "library", @@ -298,7 +298,7 @@ ], "description": "PHP/SAP integration tests.", "homepage": "https://php-sap.github.io/", - "time": "2019-01-23T12:34:30+00:00" + "time": "2020-02-03T13:59:15+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -448,38 +448,38 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "v1.10.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -507,7 +507,7 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2020-01-20T15:57:02+00:00" }, { "name": "phpunit/php-code-coverage", @@ -883,8 +883,58 @@ "mock", "xunit" ], + "abandoned": true, "time": "2015-10-02T06:51:40+00:00" }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, { "name": "sebastian/comparator", "version": "1.2.4", @@ -1259,16 +1309,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -1280,7 +1330,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -1297,12 +1347,12 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { - "name": "Gert de Pagter", - "email": "backendtea@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -1313,20 +1363,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.21", + "version": "v3.4.37", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea" + "reference": "aa46bc2233097d5212332c907f9911533acfbf80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/554a59a1ccbaac238a89b19c8e551a556fd0e2ea", - "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea", + "url": "https://api.github.com/repos/symfony/yaml/zipball/aa46bc2233097d5212332c907f9911533acfbf80", + "reference": "aa46bc2233097d5212332c907f9911533acfbf80", "shasum": "" }, "require": { @@ -1372,36 +1422,33 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-01-01T13:45:19+00:00" + "time": "2020-01-13T08:00:59+00:00" }, { "name": "webmozart/assert", - "version": "1.4.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", "shasum": "" }, "require": { "php": "^5.3.3 || ^7.0", "symfony/polyfill-ctype": "^1.8" }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -1423,7 +1470,7 @@ "check", "validate" ], - "time": "2018-12-25T11:19:39+00:00" + "time": "2019-11-24T13:36:37+00:00" } ], "aliases": [], @@ -1432,7 +1479,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "~5.5.0", + "php": "^5.5", "ext-saprfc": "*" }, "platform-dev": { diff --git a/src/AbstractRemoteFunctionCall.php b/src/AbstractRemoteFunctionCall.php deleted file mode 100644 index 24afc65..0000000 --- a/src/AbstractRemoteFunctionCall.php +++ /dev/null @@ -1,37 +0,0 @@ -function === null) { + /** + * Create a new function resource. + */ + $this->function = @saprfc_function_discover( + $this->getConnection(), + $this->getName() + ); + if ($this->function === false) { + $this->function = null; + throw new UnknownFunctionException(sprintf( + 'Unknown function %s: %s', + $this->getName(), + @saprfc_error() + )); + } + } + return $this->function; + } + + /** + * Open a connection in case it hasn't been done yet and return the + * connection resource. + * @return resource + * @throws ConnectionFailedException + * @throws IncompleteConfigException + */ + protected function getConnection() + { + if ($this->connection === null) { + /** + * In case the is no configuration, throw an exception. + */ + if (($config = $this->getConfiguration()) === null) { + throw new IncompleteConfigException( + 'Configuration is missing!' + ); + } + /** + * Catch generic IIncompleteConfigException interface and throw the + * actual exception class of this repository. + */ + try { + $moduleConfig = $this->getModuleConfig($config); + } catch (IIncompleteConfigException $exception) { + throw new IncompleteConfigException( + $exception->getMessage(), + $exception->getCode(), + $exception + ); + } + /** + * Create a new connection resource. + */ + $this->connection = @saprfc_open($moduleConfig); + /** + * In case the connection couldn't be opened, throw an exception. + */ + if ($this->connection === false) { + $this->connection = null; + throw new ConnectionFailedException(sprintf( + 'Connection creation failed: %s', + @saprfc_error() + )); + } + } + return $this->connection; + } + + /** + * Module specific destructor. + */ + public function __destruct() + { + if ($this->function !== null) { + @saprfc_function_free($this->function); + $this->function = null; + } + if ($this->connection !== null) { + @saprfc_close($this->connection); + $this->connection = null; + } + } + + /** + * Connect to the SAP remote system and retrieve the API of the SAP remote + * function. This ignores any API settings in this class. + * @return RemoteApi + * @throws ConnectionFailedException + * @throws IncompleteConfigException + * @throws UnknownFunctionException + * @throws SapLogicException + */ + public function extractApi() + { + $api = new RemoteApi(); + foreach ($this->saprfcFunctionInterface() as $element) { + $api->add($this->createApiValue( + strtoupper($element['name']), + $this->typeToDirection($element['type']), + (bool)$element['optional'], + $element['def'] + )); + } + return $api; + } + + /** + * Get remote function call API definition. + * @return array The array describing the remote function call API. + * @throws ConnectionFailedException + * @throws IncompleteConfigException + * @throws UnknownFunctionException + */ + protected function saprfcFunctionInterface() + { + $definitions = @saprfc_function_interface($this->getFunction()); + if ($definitions === false) { + throw new ConnectionFailedException( + 'Cannot query remote function API!' + ); + } + return $definitions; + } + + /** + * Invoke the SAP remote function call with all parameters. + * Attention: A configuration is necessary to invoke a SAP remote function + * call! + * @return array + * @throws ArrayElementMissingException + * @throws ConnectionFailedException + * @throws FunctionCallException + * @throws IncompleteConfigException + * @throws UnknownFunctionException + */ + public function invoke() + { + $this->setSapRfcInputValues($this->getApi()->getInputValues(), $this->getParams()); + $this->setSapRfcTables($this->getApi()->getTables(), $this->getParams()); + $result = @saprfc_call_and_receive($this->getFunction()); + if ($result !== 0) { + throw new FunctionCallException(sprintf( + 'Function call %s failed: %s', + $this->getName(), + @saprfc_exception($this->getFunction()) + )); + } + return $this->getSaprfcResults(); + } + + /** + * Set all input values. + * @param array $inputs The array of input parameters to set. + * @param array $params + * @return void + * @throws ConnectionFailedException + * @throws FunctionCallException + * @throws IncompleteConfigException + * @throws UnknownFunctionException + */ + private function setSapRfcInputValues($inputs, $params) + { + foreach ($inputs as $input) { + $key = $input->getName(); + if (array_key_exists($key, $params)) { + $value = $params[$key]; + if (!@saprfc_import($this->getFunction(), $key, $value)) { + throw new FunctionCallException(sprintf( + 'Function call %s failed: Assigning param %s failed. Expected type %s, actual type %s.', + $this->getName(), + $key, + $input->getType(), + gettype($value) + )); + } + } elseif (!$input->isOptional()) { + throw new FunctionCallException(sprintf( + 'Missing parameter \'%s\' for function call \'%s\'!', + $key, + $this->getName() + )); + } + } + } + + /** + * Initializes the table parameters of the remote function call. + * @param array $tables The array of table parameters to initialize. + * @param array $params + * @return void + * @throws ConnectionFailedException + * @throws FunctionCallException In case the initialization fails. + * @throws IncompleteConfigException + * @throws UnknownFunctionException + */ + private function setSapRfcTables($tables, $params) + { + foreach ($tables as $table) { + $key = $table->getName(); + /** + * Initialize each table. + */ + if (!@saprfc_table_init($this->getFunction(), $key)) { + throw new FunctionCallException(sprintf( + 'Initializing table %s for function %s failed!', + $key, + $this->getName() + )); + } + /** + * Fill the prepared table in case there are values in the parameters. + */ + if ( + array_key_exists($key, $params) + && is_array($params[$key]) + && count($params[$key]) > 0 + ) { + foreach ($params[$key] as $number => $row) { + if (!@saprfc_table_append($this->getFunction(), $key, $row)) { + throw new FunctionCallException(sprintf( + 'Adding row #%u to table %s for function %s failed!', + $number, + $key, + $this->getName() + )); + } + } + } + } + } + + /** + * Import results from the function call. + * @return array + * @throws ConnectionFailedException + * @throws IncompleteConfigException + * @throws UnknownFunctionException + * @throws ArrayElementMissingException + */ + private function getSaprfcResults() + { + $result = []; + foreach ($this->getApi()->getOutputValues() as $output) { + $key = $output->getName(); + $value = @saprfc_export($this->getFunction(), $key); + $result[$key] = $output->cast($value); + unset($key, $value); + } + foreach ($this->getApi()->getTables() as $table) { + $key = $table->getName(); + $rows = []; + $max = @saprfc_table_rows($this->getFunction(), $key); + for ($index = 1; $index <= $max; $index++) { + $rows[] = @saprfc_table_read($this->getFunction(), $key, $index); + } + $result[$key] = $table->cast($rows); + unset($key, $rows, $max, $index); + } + return $result; + } +} diff --git a/src/SapRfcConfigA.php b/src/SapRfcConfigA.php deleted file mode 100644 index 0ea19b4..0000000 --- a/src/SapRfcConfigA.php +++ /dev/null @@ -1,44 +0,0 @@ - true, - 'SYSNR' => true, - 'CLIENT' => true, - 'USER' => true, - 'PASSWD' => true, - 'GWHOST' => false, - 'GWSERV' => false, - 'LANG' => false, - 'TRACE' => false - ]; - - use SapRfcConfigTrait; -} diff --git a/src/SapRfcConfigB.php b/src/SapRfcConfigB.php deleted file mode 100644 index 3ecbf54..0000000 --- a/src/SapRfcConfigB.php +++ /dev/null @@ -1,43 +0,0 @@ - true, - 'USER' => true, - 'PASSWD' => true, - 'MSHOST' => true, - 'R3NAME' => false, - 'GROUP' => false, - 'LANG' => false, - 'TRACE' => false - ]; - - use SapRfcConfigTrait; -} diff --git a/src/SapRfcConfigTrait.php b/src/SapRfcConfigTrait.php deleted file mode 100644 index 29ccf46..0000000 --- a/src/SapRfcConfigTrait.php +++ /dev/null @@ -1,49 +0,0 @@ - $mandatory) { - $keyLower = strtolower($key); - if ($this->has($keyLower)) { - $method = sprintf('get%s', ucfirst($keyLower)); - $config[$key] = $this->{$method}(); - } elseif ($mandatory === true) { - throw new IncompleteConfigException(sprintf( - 'Missing mandatory key %s.', - $keyLower - )); - } - } - return $config; - } -} diff --git a/src/SapRfcConnection.php b/src/SapRfcConnection.php deleted file mode 100644 index a089c1d..0000000 --- a/src/SapRfcConnection.php +++ /dev/null @@ -1,88 +0,0 @@ -createFunctionInstance('RFC_PING'); - try { - $ping->invoke(); - } catch (FunctionCallException $fcex) { - return false; - } - return true; - } - - /** - * Closes the connection instance of the underlying PHP module. - */ - public function close() - { - if ($this->isConnected()) { - @saprfc_close($this->connection); - $this->connection = null; - } - } - - /** - * Prepare a remote function call and return a function instance. - * @param string $name - * @return \phpsap\saprfc\SapRfcFunction - * @throws \phpsap\exceptions\ConnectionFailedException - */ - protected function createFunctionInstance($name) - { - return new SapRfcFunction($this->getConnection(), $name); - } - - /** - * Creates a connection using the underlying PHP module. - * @throws \phpsap\exceptions\ConnectionFailedException - */ - public function connect() - { - if ($this->isConnected()) { - $this->close(); - } - $this->connection = @saprfc_open($this->config); - if ($this->connection === false) { - $this->connection = null; - throw new ConnectionFailedException(sprintf( - 'Connection %s creation failed: %s', - $this->getId(), - @saprfc_error() - )); - } - } -} diff --git a/src/SapRfcFunction.php b/src/SapRfcFunction.php deleted file mode 100644 index a45f5db..0000000 --- a/src/SapRfcFunction.php +++ /dev/null @@ -1,267 +0,0 @@ -setSaprfcParameters(); - $result = @saprfc_call_and_receive($this->function); - if ($result !== 0) { - throw new FunctionCallException(sprintf( - 'Function call %s failed: %s', - $this->getName(), - @saprfc_exception($this->function) - )); - } - return $this->getSaprfcResults(); - } - - /** - * Close remote function call and clear its interface. - */ - public function __destruct() - { - if ($this->functionInterface !== null) { - $this->functionInterface = null; - } - if ($this->function !== null) { - @saprfc_function_free($this->function); - $this->function = null; - } - } - - /** - * Set function call parameter. - * All parameter names will be converted to upper case. - * @param string $name - * @param array|string|float|int|bool|null $value - * @return \phpsap\interfaces\IFunction $this - * @throws \InvalidArgumentException - */ - public function setParam($name, $value) - { - return parent::setParam( - strtoupper($name), - $value - ); - } - - /** - * Create a remote function call resource. - * @return mixed - * @throws \phpsap\exceptions\UnknownFunctionException - */ - protected function getFunction() - { - $function = @saprfc_function_discover($this->connection, $this->getName()); - if ($function === false) { - $function = null; - throw new UnknownFunctionException(sprintf( - 'Unknown function %s: %s', - $this->getName(), - @saprfc_error() - )); - } - return $function; - } - - /** - * Retrieve the interface of the remote function. - * @return array - */ - protected function getRemoteInterface() - { - if ($this->functionInterface === null) { - $this->functionInterface = []; - foreach ($this->saprfcFunctionInterface() as $element) { - $this->importRemoteInterfaceElement($element); - } - } - return $this->functionInterface; - } - - /** - * Import a remote function call interface element. - * @param array $element - */ - private function importRemoteInterfaceElement($element) - { - $name = strtoupper($element['name']); - $type = $this->remoteInterfaceType($element['type'], $element['def']); - $members = $element['def']; - $this->functionInterface[$name] = ['type' => $type, 'members' => $members]; - } - - /** - * Get the remote interface definition type. - * @param string $type - * @param array $def - * @return string - */ - private function remoteInterfaceType($type, $def) - { - if ($type !== 'TABLE' - && isset($def[0]['name']) - && $def[0]['name'] !== '' - ) { - return sprintf('%s_STRUCT', $type); - } - return $type; - } - - /** - * Get remote function call interface definition. - * @return array - */ - private function saprfcFunctionInterface() - { - $definitions = @saprfc_function_interface($this->function); - if ($definitions === false) { - return []; - } - return $definitions; - } - - /** - * Export all function call parameters. - * @throws \LogicException - */ - private function setSaprfcParameters() - { - foreach ($this->getRemoteInterface() as $name => $definition) { - $result = $this->setSapRfcParameter($name, $definition['type'], $definition['members']); - if ($result !== true) { - throw new \LogicException(sprintf( - 'Assigning param %s, type %s, value %s to function %s failed.', - $name, - $definition['type'], - gettype($this->getParam($name)), - $this->getName() - )); - } - } - } - - /** - * Export a single function call parameter. - * @param string $name The remote function call parameter name. - * @param string $type The remote function call parameter type. - * @param array $members The members of a remote function call parameter. - * @return bool success? - * @throws \LogicException - */ - private function setSapRfcParameter($name, $type, $members) - { - switch ($type) { - case 'IMPORT': - $param = $this->getParam($name, ''); - $result = @saprfc_import($this->function, $name, $param); - break; - case 'IMPORT_STRUCT': - $param = $this->getParam($name, []); - foreach ($members as $member) { - if (!array_key_exists($member, $param)) { - $param[$member] = ''; - } - } - $result = @saprfc_import($this->function, $name, $param); - break; - case 'TABLE': - $result = @saprfc_table_init($this->function, $name); - break; - case 'EXPORT': //fall through - case 'EXPORT_STRUCT': - $result = true; - break; - default: - throw new \LogicException(sprintf( - 'Unknown type %s in interface of function %s.', - $type, - $this->getName() - )); - } - return $result; - } - - /** - * Import results from the function call. - * @return array - * @throws \LogicException - */ - private function getSaprfcResults() - { - $result = []; - foreach ($this->getRemoteInterface() as $name => $definition) { - switch ($definition['type']) { - case 'IMPORT': //fall through - case 'IMPORT_STRUCT': - break; - case 'EXPORT': //fall through - case 'EXPORT_STRUCT': - $result[$name] = trim(@saprfc_export($this->function, $name)); - break; - case 'TABLE': - $result[$name] = []; - $max = @saprfc_table_rows($this->function, $this->getName()); - for ($index = 1; $index <= $max; $index++) { - $result[$name][] = @saprfc_table_read($this->function, $this->getName(), $index); - } - break; - default: - throw new \LogicException(sprintf( - 'Unknown type %s in interface of function %s.', - $definition['type'], - $this->getName() - )); - } - } - return $result; - } -} diff --git a/src/Traits/ApiTrait.php b/src/Traits/ApiTrait.php new file mode 100644 index 0000000..9bb8ca5 --- /dev/null +++ b/src/Traits/ApiTrait.php @@ -0,0 +1,103 @@ +createMembers($def)); + } + if ($def[0]['name'] !== '') { + return new Struct($name, $direction, $optional, $this->createMembers($def)); + } + return new Value($this->abapToType($def[0]['abap']), $name, $direction, $optional); + } + + /** + * Create either struct or table members from the def array of the remote function API. + * @param array $members + * @return Element[] An array of IElement compatible objects. + * @throws SapLogicException In case a datatype is missing in the mappings array. + */ + private function createMembers($members) + { + $result = []; + foreach ($members as $member) { + $result[] = new Element($this->abapToType($member['abap']), $member['name']); + } + return $result; + } + + /** + * Convert SAPRFC abap datatype to PHP-SAP datatype. + * @param string $type The ABAP data type from the API definition. + * @return string The PHP/SAP internal data type. + * @throws SapLogicException In case a datatype is missing in the mappings array. + */ + private function abapToType($type) + { + static $mapping = [ + 'b' => Element::TYPE_INTEGER, //1-byte integer (internal) + 's' => Element::TYPE_INTEGER, //2-byte integer (internal) + 'I' => Element::TYPE_INTEGER, //4-byte integer + 'INT8' => Element::TYPE_INTEGER, //8-byte integer + 'P' => Element::TYPE_FLOAT, //packed number 1-16 bytes + 'DECFLOAT16' => Element::TYPE_FLOAT, //floating point with 16 positions + 'DECFLOAT34' => Element::TYPE_FLOAT, //floating point with 34 positions + 'F' => Element::TYPE_FLOAT, //binary floating point with 17 positions + 'C' => Element::TYPE_STRING, //fixed length text field + 'N' => Element::TYPE_INTEGER, //fixed length numeric text field 1-262143 positions + 'STRING' => Element::TYPE_STRING, //text string + 'X' => Element::TYPE_HEXBIN, //fixed length hexadecimal encoded binary data + 'XSTRING' => Element::TYPE_HEXBIN, //fixed length hexadecimal encoded binary data + 'D' => Element::TYPE_DATE, //date field + 'T' => Element::TYPE_TIME, //time field + ]; + if (!array_key_exists($type, $mapping)) { + throw new SapLogicException(sprintf('Unknown SAP data type \'%s\'!', $type)); + } + return $mapping[$type]; + } + + /** + * Convert SAPRFC type to PHP/SAP direction. + * @param string $type The remote function parameter type indicating whether it's an input or a return parameter. + * @return string The PHP/SAP internal direction. + * @throws SapLogicException In case the given SAP RFC type is missing in the static mapping. + */ + private function typeToDirection($type) + { + static $mapping = [ + 'IMPORT' => Value::DIRECTION_INPUT, //SAP remote function input parameter + 'EXPORT' => Value::DIRECTION_OUTPUT, //SAP remote function return value or struct + 'TABLE' => Table::DIRECTION_TABLE //SAP remote function return table + ]; + if (!array_key_exists($type, $mapping)) { + throw new SapLogicException(sprintf('Unknown SAPRFC type \'%s\'!', $type)); + } + return $mapping[$type]; + } +} diff --git a/src/Traits/ConfigTrait.php b/src/Traits/ConfigTrait.php new file mode 100644 index 0000000..a7899de --- /dev/null +++ b/src/Traits/ConfigTrait.php @@ -0,0 +1,116 @@ +getCommonConfig($config); + /** + * Only type A and B configurations are supported by this module, + * its common classes and its interface. Therefore, we do not + * expect any other types here. + */ + if ($config instanceof IConfigTypeA) { + $specific = $this->getTypeAConfig($config); + } else { + $specific = $this->getTypeBConfig($config); + } + //Once we end up here, the configuration is complete. + return array_merge($common, $specific); + } + + /** + * Get the common configuration for the saprfc module. + * + * I chose a "stupid" (and repetitive) way because it is more readable + * and thus better maintainable for others than an "intelligent" way. + * + * @param IConfigCommon $config + * @return array + * @throws IIncompleteConfigException + */ + private function getCommonConfig(IConfigCommon $config) + { + $common = []; + if ($config->getLang() !== null) { + $common['LANG'] = $config->getLang(); + } + if ($config->getTrace() !== null) { + $common['TRACE'] = $config->getTrace(); + } + $common['CLIENT'] = $config->getClient(); + $common['USER'] = $config->getUser(); + $common['PASSWD'] = $config->getPasswd(); + return $common; + } + + /** + * Get the connection type A configuration for the saprfc module. + * + * I chose a "stupid" (and repetitive) way because it is more readable + * and thus better maintainable for others than an "intelligent" way. + * + * @param IConfigTypeA $config + * @return array + * @throws IIncompleteConfigException + */ + private function getTypeAConfig(IConfigTypeA $config) + { + $typeA = []; + if ($config->getGwhost() !== null) { + $typeA['GWHOST'] = $config->getGwhost(); + } + if ($config->getGwserv() !== null) { + $typeA['GWSERV'] = $config->getGwserv(); + } + $typeA['ASHOST'] = $config->getAshost(); + $typeA['SYSNR'] = $config->getSysnr(); + return $typeA; + } + + /** + * Get the connection type B configuration for the saprfc module. + * + * I chose a "stupid" (and repetitive) way because it is more readable + * and thus better maintainable for others than an "intelligent" way. + * + * @param IConfigTypeB $config + * @return array + * @throws IIncompleteConfigException + */ + private function getTypeBConfig(IConfigTypeB $config) + { + $typeB = []; + if ($config->getR3name() !== null) { + $typeB['R3NAME'] = $config->getR3name(); + } + if ($config->getGroup() !== null) { + $typeB['GROUP'] = $config->getGroup(); + } + $typeB['MSHOST'] = $config->getMshost(); + return $typeB; + } +} diff --git a/tests/AbstractRemoteFunctionCallTest.php b/tests/AbstractRemoteFunctionCallTest.php deleted file mode 100644 index aed02b0..0000000 --- a/tests/AbstractRemoteFunctionCallTest.php +++ /dev/null @@ -1,45 +0,0 @@ - 'sap.example.com', - 'sysnr' => '001', - 'client' => '002', - 'user' => 'username', - 'passwd' => 'password' - ]); - $rfc = new RemoteFunctionCall($config); - $connection = $rfc->createConnectionInstance($config); - static::assertInstanceOf(SapRfcConnection::class, $connection); - } -} diff --git a/tests/SapRfcConfigATest.php b/tests/SapRfcConfigATest.php deleted file mode 100644 index 6a60eba..0000000 --- a/tests/SapRfcConfigATest.php +++ /dev/null @@ -1,83 +0,0 @@ - [ - 'name' => 'EV_FRIDAY', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 1 => [ - 'name' => 'EV_FRIDAY_LAST', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 2 => [ - 'name' => 'EV_FRIDAY_NEXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 3 => [ - 'name' => 'EV_FRITXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'C', - 'len' => 15, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 4 => [ - 'name' => 'EV_MONDAY', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 5 => [ - 'name' => 'EV_MONDAY_LAST', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 6 => [ - 'name' => 'EV_MONDAY_NEXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 7 => [ - 'name' => 'EV_MONTH', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 2, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 8 => [ - 'name' => 'EV_MONTH_LAST_DAY', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 9 => [ - 'name' => 'EV_MONTXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'C', - 'len' => 15, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 10 => [ - 'name' => 'EV_TIMESTAMP', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'C', - 'len' => 14, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 11 => [ - 'name' => 'EV_WEEK', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 6, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 12 => [ - 'name' => 'EV_WEEK_LAST', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 6, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 13 => [ - 'name' => 'EV_WEEK_NEXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 6, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 14 => [ - 'name' => 'EV_YEAR', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 4, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 15 => [ - 'name' => 'IV_DATE', - 'type' => 'IMPORT', - 'optional' => 1, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - ]; - } - return false; - }); - static::mock('saprfc_call_and_receive', function ($function) { - if ($function === 'SAPRFC Z_MC_GET_DATE_TIME') { - return 0; - } - return 1; - }); - static::mock('saprfc_import', function ($function, $name, $param) { - return ($function === 'SAPRFC Z_MC_GET_DATE_TIME' - && $name === 'IV_DATE' - && $param === '20181119' - ); - }); - static::mock('saprfc_export', function ($function, $name) { - if ($function !== 'SAPRFC Z_MC_GET_DATE_TIME') { - return ''; - } - switch ($name) { - case 'EV_FRIDAY': - return '20181123'; - case 'EV_FRIDAY_LAST': - return '20181116'; - case 'EV_FRIDAY_NEXT': - return '20181130'; - case 'EV_FRITXT': - return 'Freitag'; - case 'EV_MONDAY': - return '20181119'; - case 'EV_MONDAY_LAST': - return '20181112'; - case 'EV_MONDAY_NEXT': - return '20181126'; - case 'EV_MONTH': - return '11'; - case 'EV_MONTH_LAST_DAY': - return '20181130'; - case 'EV_MONTXT': - return 'Montag'; - case 'EV_TIMESTAMP': - return '201811190000000'; - case 'EV_WEEK': - return '201847'; - case 'EV_WEEK_LAST': - return '201846'; - case 'EV_WEEK_NEXT': - return '201848'; - case 'EV_YEAR': - return '2018'; - default: - return ''; - } - }); - static::mock('saprfc_function_free', function (&$function) { - $function = null; - }); - } - - /** - * Mock the SAP RFC module for a failed SAP remote function call with parameters. - */ - protected function mockFailedRemoteFunctionCallWithParameters() - { - static::mock('saprfc_open', function ($config) { - if (is_array($config)) { - return 'SAPRFC CONNECTION'; - } - return false; - }); - static::mock('saprfc_close', function (&$connection) { - $connection = null; - }); - static::mock('saprfc_function_discover', function ($connection, $name) { - if ($connection === 'SAPRFC CONNECTION' && $name === 'Z_MC_GET_DATE_TIME') { - return 'SAPRFC Z_MC_GET_DATE_TIME'; - } - return false; - }); - static::mock('saprfc_function_interface', function ($function) { - if ($function === 'SAPRFC Z_MC_GET_DATE_TIME') { - return [ - 0 => [ - 'name' => 'EV_FRIDAY', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 1 => [ - 'name' => 'EV_FRIDAY_LAST', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 2 => [ - 'name' => 'EV_FRIDAY_NEXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 3 => [ - 'name' => 'EV_FRITXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => [ - 0 => [ - 'name' => '', - 'abap' => 'C', - 'len' => 15, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 4 => [ - 'name' => 'EV_MONDAY', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 5 => [ - 'name' => 'EV_MONDAY_LAST', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 6 => [ - 'name' => 'EV_MONDAY_NEXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 7 => [ - 'name' => 'EV_MONTH', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 2, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 8 => [ - 'name' => 'EV_MONTH_LAST_DAY', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 9 => [ - 'name' => 'EV_MONTXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'C', - 'len' => 15, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 10 => [ - 'name' => 'EV_TIMESTAMP', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'C', - 'len' => 14, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 11 => [ - 'name' => 'EV_WEEK', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 6, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 12 => [ - 'name' => 'EV_WEEK_LAST', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 6, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 13 => [ - 'name' => 'EV_WEEK_NEXT', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 6, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 14 => [ - 'name' => 'EV_YEAR', - 'type' => 'EXPORT', - 'optional' => 0, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'N', - 'len' => 4, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - 15 => [ - 'name' => 'IV_DATE', - 'type' => 'IMPORT', - 'optional' => 1, - 'def' => - [ - 0 => - [ - 'name' => '', - 'abap' => 'D', - 'len' => 8, - 'dec' => 0, - 'offset' => 0, - ], - ], - ], - ]; - } - return false; - }); - static::mock('saprfc_call_and_receive', function ($function) { - if ($function === 'SAPRFC Z_MC_GET_DATE_TIME') { - return 1; - } - throw new \RuntimeException('Unexpected function instance.'); - }); - static::mock('saprfc_import', function ($function, $name, $param) { - return ($function === 'SAPRFC Z_MC_GET_DATE_TIME' - && $name === 'IV_DATE' - && $param === '2018-11-19' - ); - }); - static::mock('saprfc_function_free', function (&$function) { - $function = null; - }); - } -} diff --git a/tests/SapRfcIntegrationTest.php b/tests/SapRfcIntegrationTest.php new file mode 100644 index 0000000..eedeb00 --- /dev/null +++ b/tests/SapRfcIntegrationTest.php @@ -0,0 +1,698 @@ + + [ + 'name' => 'TEST_OUT', + 'type' => 'EXPORT', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'RFCFLOAT', + 'abap' => 'F', + 'len' => 8, + 'dec' => 0, + 'offset' => 0, + ], + 1 => + [ + 'name' => 'RFCCHAR1', + 'abap' => 'C', + 'len' => 1, + 'dec' => 0, + 'offset' => 8, + ], + 2 => + [ + 'name' => 'RFCINT2', + 'abap' => 's', + 'len' => 2, + 'dec' => 0, + 'offset' => 10, + ], + 3 => + [ + 'name' => 'RFCINT1', + 'abap' => 'b', + 'len' => 1, + 'dec' => 0, + 'offset' => 12, + ], + 4 => + [ + 'name' => 'RFCCHAR4', + 'abap' => 'C', + 'len' => 4, + 'dec' => 0, + 'offset' => 13, + ], + 5 => + [ + 'name' => 'RFCINT4', + 'abap' => 'I', + 'len' => 4, + 'dec' => 0, + 'offset' => 20, + ], + 6 => + [ + 'name' => 'RFCHEX3', + 'abap' => 'X', + 'len' => 3, + 'dec' => 0, + 'offset' => 24, + ], + 7 => + [ + 'name' => 'RFCCHAR2', + 'abap' => 'C', + 'len' => 2, + 'dec' => 0, + 'offset' => 27, + ], + 8 => + [ + 'name' => 'RFCTIME', + 'abap' => 'T', + 'len' => 6, + 'dec' => 0, + 'offset' => 29, + ], + 9 => + [ + 'name' => 'RFCDATE', + 'abap' => 'D', + 'len' => 8, + 'dec' => 0, + 'offset' => 35, + ], + 10 => + [ + 'name' => 'RFCDATA1', + 'abap' => 'C', + 'len' => 50, + 'dec' => 0, + 'offset' => 43, + ], + 11 => + [ + 'name' => 'RFCDATA2', + 'abap' => 'C', + 'len' => 50, + 'dec' => 0, + 'offset' => 93, + ], + ], + ], + 1 => + [ + 'name' => 'TEST_IN', + 'type' => 'IMPORT', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'RFCFLOAT', + 'abap' => 'F', + 'len' => 8, + 'dec' => 0, + 'offset' => 0, + ], + 1 => + [ + 'name' => 'RFCCHAR1', + 'abap' => 'C', + 'len' => 1, + 'dec' => 0, + 'offset' => 8, + ], + 2 => + [ + 'name' => 'RFCINT2', + 'abap' => 's', + 'len' => 2, + 'dec' => 0, + 'offset' => 10, + ], + 3 => + [ + 'name' => 'RFCINT1', + 'abap' => 'b', + 'len' => 1, + 'dec' => 0, + 'offset' => 12, + ], + 4 => + [ + 'name' => 'RFCCHAR4', + 'abap' => 'C', + 'len' => 4, + 'dec' => 0, + 'offset' => 13, + ], + 5 => + [ + 'name' => 'RFCINT4', + 'abap' => 'I', + 'len' => 4, + 'dec' => 0, + 'offset' => 20, + ], + 6 => + [ + 'name' => 'RFCHEX3', + 'abap' => 'X', + 'len' => 3, + 'dec' => 0, + 'offset' => 24, + ], + 7 => + [ + 'name' => 'RFCCHAR2', + 'abap' => 'C', + 'len' => 2, + 'dec' => 0, + 'offset' => 27, + ], + 8 => + [ + 'name' => 'RFCTIME', + 'abap' => 'T', + 'len' => 6, + 'dec' => 0, + 'offset' => 29, + ], + 9 => + [ + 'name' => 'RFCDATE', + 'abap' => 'D', + 'len' => 8, + 'dec' => 0, + 'offset' => 35, + ], + 10 => + [ + 'name' => 'RFCDATA1', + 'abap' => 'C', + 'len' => 50, + 'dec' => 0, + 'offset' => 43, + ], + 11 => + [ + 'name' => 'RFCDATA2', + 'abap' => 'C', + 'len' => 50, + 'dec' => 0, + 'offset' => 93, + ], + ], + ], + 2 => + [ + 'name' => 'DESTINATIONS', + 'type' => 'TABLE', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'RFCDEST', + 'abap' => 'C', + 'len' => 32, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 3 => + [ + 'name' => 'LOG', + 'type' => 'TABLE', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'RFCDEST', + 'abap' => 'C', + 'len' => 32, + 'dec' => 0, + 'offset' => 0, + ], + 1 => + [ + 'name' => 'RFCWHOAMI', + 'abap' => 'C', + 'len' => 32, + 'dec' => 0, + 'offset' => 32, + ], + 2 => + [ + 'name' => 'RFCLOG', + 'abap' => 'C', + 'len' => 70, + 'dec' => 0, + 'offset' => 64, + ], + ], + ], + ]; + + /** + * @var array The raw API of RFC_READ_TABLE as seen by the module. + */ + public static $rfcReadTableApi = [ + 0 => + [ + 'name' => 'DELIMITER', + 'type' => 'IMPORT', + 'optional' => 1, + 'def' => + [ + 0 => + [ + 'name' => '', + 'abap' => 'C', + 'len' => 1, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 1 => + [ + 'name' => 'NO_DATA', + 'type' => 'IMPORT', + 'optional' => 1, + 'def' => + [ + 0 => + [ + 'name' => '', + 'abap' => 'C', + 'len' => 1, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 2 => + [ + 'name' => 'QUERY_TABLE', + 'type' => 'IMPORT', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => '', + 'abap' => 'C', + 'len' => 30, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 3 => + [ + 'name' => 'ROWCOUNT', + 'type' => 'IMPORT', + 'optional' => 1, + 'def' => + [ + 0 => + [ + 'name' => '', + 'abap' => 'I', + 'len' => 4, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 4 => + [ + 'name' => 'ROWSKIPS', + 'type' => 'IMPORT', + 'optional' => 1, + 'def' => + [ + 0 => + [ + 'name' => '', + 'abap' => 'I', + 'len' => 4, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 5 => + [ + 'name' => 'DATA', + 'type' => 'TABLE', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'WA', + 'abap' => 'C', + 'len' => 512, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + 6 => + [ + 'name' => 'FIELDS', + 'type' => 'TABLE', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'FIELDNAME', + 'abap' => 'C', + 'len' => 30, + 'dec' => 0, + 'offset' => 0, + ], + 1 => + [ + 'name' => 'OFFSET', + 'abap' => 'N', + 'len' => 6, + 'dec' => 0, + 'offset' => 30, + ], + 2 => + [ + 'name' => 'LENGTH', + 'abap' => 'N', + 'len' => 6, + 'dec' => 0, + 'offset' => 36, + ], + 3 => + [ + 'name' => 'TYPE', + 'abap' => 'C', + 'len' => 1, + 'dec' => 0, + 'offset' => 42, + ], + 4 => + [ + 'name' => 'FIELDTEXT', + 'abap' => 'C', + 'len' => 60, + 'dec' => 0, + 'offset' => 43, + ], + ], + ], + 7 => + [ + 'name' => 'OPTIONS', + 'type' => 'TABLE', + 'optional' => 0, + 'def' => + [ + 0 => + [ + 'name' => 'TEXT', + 'abap' => 'C', + 'len' => 72, + 'dec' => 0, + 'offset' => 0, + ], + ], + ], + ]; + + /** + * @inheritDoc + */ + protected function mockConnectionFailed() + { + static::mock('saprfc_open', static function ($config) { + unset($config); + return false; + }); + static::mock('saprfc_error', static function () { + return 'my error message'; + }); + } + + /** + * @inheritDoc + */ + protected function mockSuccessfulRfcPing() + { + static::mock('saprfc_open', static function ($config) { + if (is_array($config)) { + return 'SAPRFC CONNECTION'; + } + return false; + }); + static::mock('saprfc_close', static function (&$connection) { + $connection = null; + }); + static::mock('saprfc_function_discover', static function ($connection, $name) { + if ($connection === 'SAPRFC CONNECTION' && $name === 'RFC_PING') { + return 'SAPRFC PING'; + } + return false; + }); + static::mock('saprfc_call_and_receive', static function ($function) { + if ($function === 'SAPRFC PING') { + return 0; + } + return 1; + }); + static::mock('saprfc_function_free', static function (&$function) { + $function = null; + }); + static::mock('saprfc_function_interface', static function ($function) { + if ($function === 'SAPRFC PING') { + return []; + } + return false; + }); + } + + /** + * @inheritDoc + */ + protected function mockUnknownFunctionException() + { + static::mock('saprfc_open', static function ($config) { + if (is_array($config)) { + return 'SAPRFC CONNECTION'; + } + return false; + }); + static::mock('saprfc_close', static function (&$connection) { + $connection = null; + }); + static::mock('saprfc_function_discover', static function ($connection, $name) { + if ($connection === 'SAPRFC CONNECTION' && $name === 'RFC_PINGG') { + return false; + } + return $name; + }); + static::mock('saprfc_error', static function () { + return 'function RFC_PINGG not found'; + }); + static::mock('saprfc_function_free', static function (&$function) { + $function = null; + }); + } + + /** + * @inheritDoc + */ + protected function mockRemoteFunctionCallWithParametersAndResults() + { + static::mock('saprfc_open', static function ($config) { + if (is_array($config)) { + return 'SAPRFC CONNECTION'; + } + return false; + }); + static::mock('saprfc_close', static function (&$connection) { + $connection = null; + }); + static::mock('saprfc_function_discover', static function ($connection, $name) { + if ($connection === 'SAPRFC CONNECTION' && $name === 'RFC_WALK_THRU_TEST') { + return 'SAPRFC RFC_WALK_THRU_TEST'; + } + return false; + }); + static::mock('saprfc_function_interface', static function ($function) { + if ($function === 'SAPRFC RFC_WALK_THRU_TEST') { + return static::$rfcWalkThruTestApi; + } + return false; + }); + static::mock('saprfc_call_and_receive', static function ($function) { + if ($function === 'SAPRFC RFC_WALK_THRU_TEST') { + return 0; + } + return 1; + }); + static::mock('saprfc_import', static function ($function, $name, $param) { + return ($function === 'SAPRFC RFC_WALK_THRU_TEST' + && $name === 'TEST_IN' + && is_array($param) + ); + }); + static::mock('saprfc_table_init', static function ($function, $name) { + return ($function === 'SAPRFC RFC_WALK_THRU_TEST' + && in_array($name, ['LOG', 'DESTINATIONS']) + ); + }); + static::mock('saprfc_table_append', static function ($function, $name, $param) { + return ($function === 'SAPRFC RFC_WALK_THRU_TEST' + && $name === 'DESTINATIONS' + && is_array($param) + ); + }); + static::mock('saprfc_table_append', static function ($function, $name, $param) { + return ($function === 'SAPRFC RFC_WALK_THRU_TEST' + && $name === 'DESTINATIONS' + && is_array($param) + && $param === ['RFCDEST' => 'AOP3'] + ); + }); + static::mock('saprfc_table_rows', static function ($function, $name) { + if ($function !== 'SAPRFC RFC_WALK_THRU_TEST') { + return false; + } + switch ($name) { + case 'LOG': + return 1; + case 'DESTINATIONS': + return 0; + default: + return false; + } + }); + static::mock('saprfc_table_read', static function ($function, $name, $param) { + if ($function === 'SAPRFC RFC_WALK_THRU_TEST' && $name === 'LOG' && $param === 1) { + return [ + 'RFCDEST' => 'AOP3', + 'RFCWHOAMI' => 'pzjti000', + 'RFCLOG' => 'FAP-RytEHBsRYKX AOP3 eumqvMJD ZLqovj.' //just some random characters around AOP3 + ]; + } + return false; + }); + static::mock('saprfc_export', static function ($function, $name) { + if ($function === 'SAPRFC RFC_WALK_THRU_TEST' && $name === 'TEST_OUT') { + return [ + 'RFCFLOAT' => 70.11, + 'RFCCHAR1' => 'A', + 'RFCINT2' => 4095, + 'RFCINT1' => 163, + 'RFCCHAR4' => 'QqMh', + 'RFCINT4' => 416639, + 'RFCHEX3' => '53', //=S + 'RFCCHAR2' => 'XC', + 'RFCTIME' => '102030', + 'RFCDATE' => '20191030', + 'RFCDATA1' => 'qKWjmNfad32rfS9Z', + 'RFCDATA2' => 'xi82ph2zJ8BCVtlR' + ]; + } + return false; + }); + static::mock('saprfc_function_free', static function (&$function) { + $function = null; + }); + } + + /** + * @inheritDoc + */ + protected function mockFailedRemoteFunctionCallWithParameters() + { + static::mock('saprfc_open', static function ($config) { + if (is_array($config)) { + return 'SAPRFC CONNECTION'; + } + return false; + }); + static::mock('saprfc_close', static function (&$connection) { + $connection = null; + }); + static::mock('saprfc_function_discover', static function ($connection, $name) { + if ($connection === 'SAPRFC CONNECTION' && $name === 'RFC_READ_TABLE') { + return 'SAPRFC RFC_READ_TABLE'; + } + return false; + }); + static::mock('saprfc_function_interface', static function ($function) { + if ($function === 'SAPRFC RFC_READ_TABLE') { + return static::$rfcReadTableApi; + } + return false; + }); + static::mock('saprfc_call_and_receive', static function ($function) { + if ($function === 'SAPRFC RFC_READ_TABLE') { + return 1; + } + throw new RuntimeException('Unexpected function instance.'); + }); + static::mock('saprfc_import', static function ($function, $name, $param) { + return ($function === 'SAPRFC RFC_READ_TABLE' + && $name === 'QUERY_TABLE' + && $param === '&' + ); + }); + static::mock('saprfc_table_init', static function ($function, $name) { + return ($function === 'SAPRFC RFC_READ_TABLE' + && in_array($name, ['DATA', 'FIELDS', 'OPTIONS']) + ); + }); + static::mock('saprfc_exception', static function ($function) { + if ($function === 'SAPRFC RFC_READ_TABLE') { + return 'My exception message.'; + } + throw new RuntimeException('Expected RFC_READ_TABLE function!'); + }); + static::mock('saprfc_function_free', static function (&$function) { + $function = null; + }); + } +} diff --git a/tests/SapRfcTestCaseTrait.php b/tests/TestCaseTrait.php similarity index 61% rename from tests/SapRfcTestCaseTrait.php rename to tests/TestCaseTrait.php index f3754e0..36e0043 100644 --- a/tests/SapRfcTestCaseTrait.php +++ b/tests/TestCaseTrait.php @@ -1,35 +1,34 @@ returnName; - } - - /** - * Make protected function public for testing. - * Create a connection instance using the given config. - * @param \phpsap\interfaces\IConfig $config - * @return \phpsap\interfaces\IConnection|\phpsap\saprfc\SapRfcConnection - * @throws \phpsap\interfaces\exceptions\IIncompleteConfigException - */ - public function createConnectionInstance(IConfig $config) - { - return parent::createConnectionInstance($config); - } -} diff --git a/tests/helper/saprfc.php b/tests/helper/saprfc.php index a4f539d..df305a2 100644 --- a/tests/helper/saprfc.php +++ b/tests/helper/saprfc.php @@ -18,7 +18,7 @@ /** * Close connection resource. - * @param mixed $connection Connection ressource. + * @param mixed $connection Connection resource. */ function saprfc_close(&$connection) { @@ -58,7 +58,7 @@ function saprfc_exception($function) /** * Open connection. * @param array $config - * @return ressource + * @return resource */ function saprfc_open($config) { @@ -68,9 +68,9 @@ function saprfc_open($config) /** * Get function call resource. - * @param ressource $connection Connection ressource. + * @param resource $connection Connection resource. * @param string $name Function name. - * @return ressource + * @return resource */ function saprfc_function_discover($connection, $name) { @@ -80,7 +80,7 @@ function saprfc_function_discover($connection, $name) /** * Call SAP remote function with all set parameters. - * @param ressource $function + * @param resource $function * @return int */ function saprfc_call_and_receive($function) @@ -125,6 +125,19 @@ function saprfc_table_init($function, $name) return $func($function, $name); } +/** + * Initialize saprfc table. + * @param resource $function The remote function call resource. + * @param string $name The parameter name. + * @param array $row The table row to add. + * @return bool + */ +function saprfc_table_append($function, $name, $row) +{ + $func = \phpsap\IntegrationTests\SapRfcModuleMocks::singleton()->get(__FUNCTION__); + return $func($function, $name, $row); +} + /** * Get function call result. * @param resource $function The remote function call resource.