├── LICENSE └── src ├── SapRfc.php └── Traits ├── ApiTrait.php ├── ConfigTrait.php └── ParamTrait.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 PHP/SAP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/SapRfc.php: -------------------------------------------------------------------------------- 1 | true 56 | ]; 57 | 58 | /** 59 | * Cleanup method. 60 | */ 61 | public function __destruct() 62 | { 63 | if ($this->function !== null) { 64 | $this->function = null; 65 | } 66 | if ($this->connection !== null) { 67 | $this->connection->close(); 68 | $this->connection = null; 69 | } 70 | } 71 | 72 | /** 73 | * Create a remote function call resource. 74 | * @return RemoteFunction 75 | * @throws ConnectionFailedException 76 | * @throws IncompleteConfigException 77 | * @throws UnknownFunctionException 78 | */ 79 | protected function getFunction(): RemoteFunction 80 | { 81 | if ($this->function === null) { 82 | /** 83 | * Create a new function resource. 84 | */ 85 | try { 86 | $this->function = $this 87 | ->getConnection() 88 | ->getFunction($this->getName()); 89 | } catch (ModuleFunctionCallException $exception) { 90 | throw new UnknownFunctionException(sprintf( 91 | 'Unknown function %s: %s', 92 | $this->getName(), 93 | $exception->getMessage() 94 | ), 0, $exception); 95 | } 96 | } 97 | return $this->function; 98 | } 99 | 100 | /** 101 | * Open a connection in case it hasn't been done yet and return the 102 | * connection resource. 103 | * @return Connection 104 | * @throws ConnectionFailedException 105 | * @throws IncompleteConfigException 106 | */ 107 | protected function getConnection(): Connection 108 | { 109 | if ($this->connection === null) { 110 | /** 111 | * In case the is no configuration, throw an exception. 112 | */ 113 | if (($config = $this->getConfiguration()) === null) { 114 | throw new IncompleteConfigException( 115 | 'Configuration is missing!' 116 | ); 117 | } 118 | /** 119 | * Catch generic IIncompleteConfigException interface and throw the 120 | * actual exception class of this repository. 121 | */ 122 | try { 123 | $moduleConfig = $this->getModuleConfig($config); 124 | } catch (IIncompleteConfigException $exception) { 125 | throw new IncompleteConfigException( 126 | $exception->getMessage(), 127 | $exception->getCode() 128 | ); 129 | } 130 | /** 131 | * Create a new connection resource. 132 | */ 133 | try { 134 | if ($config->getTrace() !== null) { 135 | /** 136 | * SAPNWRFC introduced TRACE_DETAILED (3) in v2.1.0 which 137 | * is not available via the interface. 138 | */ 139 | $trace = match ($config->getTrace()) { 140 | IConfiguration::TRACE_FULL => 4, 141 | IConfiguration::TRACE_VERBOSE => 2, 142 | IConfiguration::TRACE_BRIEF => 1, 143 | default => 0, 144 | }; 145 | Connection::setTraceLevel($trace); 146 | } 147 | $this->connection = new Connection($moduleConfig); 148 | } catch (TypeError | ModuleConnectionException $exception) { 149 | throw new ConnectionFailedException(sprintf( 150 | 'Connection creation failed: %s', 151 | $exception->getMessage() 152 | ), 0, $exception); 153 | } 154 | } 155 | return $this->connection; 156 | } 157 | 158 | /** 159 | * @inheritDoc 160 | */ 161 | public function extractApi(): RemoteApi 162 | { 163 | $api = new RemoteApi(); 164 | foreach ($this->saprfcFunctionInterface() as $name => $element) { 165 | try { 166 | $api->add($this->createApiElement( 167 | strtoupper($name), 168 | $this->mapType($element['type']), 169 | $this->mapDirection($element['direction']), 170 | $element 171 | )); 172 | } catch (IInvalidArgumentException | SapLogicException $exception) { 173 | /** 174 | * InvalidArgumentException is a child of SapLogicException and will 175 | * be caught too. 176 | */ 177 | throw new ConnectionFailedException( 178 | 'The API behaved unexpectedly: ' . $exception->getMessage(), 179 | $exception->getCode(), 180 | $exception 181 | ); 182 | } 183 | } 184 | return $api; 185 | } 186 | 187 | /** 188 | * Extract the remote function API from the function object and remove 189 | * unwanted variables. 190 | * @return array 191 | * @throws ConnectionFailedException 192 | * @throws IncompleteConfigException 193 | * @throws UnknownFunctionException 194 | */ 195 | public function saprfcFunctionInterface(): array 196 | { 197 | return $this->getFunction()->getFunctionDescription(); 198 | } 199 | 200 | /** 201 | * @inheritDoc 202 | * @throws IInvalidArgumentException 203 | */ 204 | public function invoke(): array 205 | { 206 | /** 207 | * Merge value and table parameters into one parameter array. 208 | */ 209 | $params = array_merge( 210 | $this->getInputParams( 211 | array_merge( 212 | $this->getApi()->getInputElements(), 213 | $this->getApi()->getChangingElements() 214 | ), 215 | $this->getParams() 216 | ), 217 | $this->getTableParams( 218 | $this->getApi()->getTables(), 219 | $this->getParams() 220 | ) 221 | ); 222 | /** 223 | * Invoke SAP remote function call. 224 | */ 225 | try { 226 | $result = $this 227 | ->getFunction() 228 | ->invoke($params, self::$invokeOptions); 229 | } catch (TypeError | ModuleFunctionCallException $exception) { 230 | throw new FunctionCallException(sprintf( 231 | 'Function call %s failed: %s', 232 | $this->getName(), 233 | $exception->getMessage() 234 | ), 0, $exception); 235 | } 236 | /** 237 | * Typecast the return values. 238 | */ 239 | return $this->castOutput(array_merge( 240 | $this->getApi()->getOutputElements(), 241 | $this->getApi()->getChangingElements(), 242 | $this->getApi()->getTables() 243 | ), $result); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/Traits/ApiTrait.php: -------------------------------------------------------------------------------- 1 | createMembers($def)); 46 | } 47 | if ($type === IStruct::TYPE_STRUCT) { 48 | return Struct::create($name, $direction, $optional, $this->createMembers($def)); 49 | } 50 | return Value::create($type, $name, $direction, $optional); 51 | } 52 | 53 | /** 54 | * Create either struct or table members from the def array of the remote function API. 55 | * @param array $def The complete API value defintion. 56 | * @return Member[] An array of Member objects. 57 | * @throws SapLogicException In case a datatype is missing in the mappings array. 58 | */ 59 | private function createMembers(array $def): array 60 | { 61 | $result = []; 62 | if (array_key_exists('typedef', $def) && is_array($def['typedef'])) { 63 | foreach ($def['typedef'] as $name => $member) { 64 | $result[] = Member::create($this->mapType($member['type']), $name); 65 | } 66 | } 67 | return $result; 68 | } 69 | 70 | /** 71 | * Convert SAP Netweaver RFC types into PHP/SAP types. 72 | * @param string $type The remote function parameter type. 73 | * @return string The PHP/SAP internal data type. 74 | * @throws SapLogicException 75 | */ 76 | private function mapType(string $type): string 77 | { 78 | $mapping = [ 79 | 'RFCTYPE_DATE' => IValue::TYPE_DATE, 80 | 'RFCTYPE_TIME' => IValue::TYPE_TIME, 81 | 'RFCTYPE_INT' => IValue::TYPE_INTEGER, 82 | 'RFCTYPE_NUM' => IValue::TYPE_INTEGER, 83 | 'RFCTYPE_INT1' => IValue::TYPE_INTEGER, 84 | 'RFCTYPE_INT2' => IValue::TYPE_INTEGER, 85 | 'RFCTYPE_INT8' => IValue::TYPE_INTEGER, 86 | 'RFCTYPE_BCD' => IValue::TYPE_STRING, 87 | 'RFCTYPE_FLOAT' => IValue::TYPE_FLOAT, 88 | 'RFCTYPE_CHAR' => IValue::TYPE_STRING, 89 | 'RFCTYPE_STRING' => IValue::TYPE_STRING, 90 | 'RFCTYPE_BYTE' => IValue::TYPE_HEXBIN, 91 | 'RFCTYPE_XSTRING' => IValue::TYPE_HEXBIN, 92 | 'RFCTYPE_STRUCTURE' => IStruct::TYPE_STRUCT, 93 | 'RFCTYPE_TABLE' => ITable::TYPE_TABLE 94 | ]; 95 | if (!array_key_exists($type, $mapping)) { 96 | throw new SapLogicException(sprintf('Unknown SAP Netweaver RFC type \'%s\'!', $type)); 97 | } 98 | return $mapping[$type]; 99 | } 100 | 101 | /** 102 | * Convert SAP Netweaver RFC directions into PHP/SAP directions. 103 | * @param string $direction The remote function parameter direction. 104 | * @return string The PHP/SAP internal direction. 105 | * @throws SapLogicException 106 | */ 107 | private function mapDirection(string $direction): string 108 | { 109 | $mapping = [ 110 | 'RFC_EXPORT' => IApiElement::DIRECTION_OUTPUT, 111 | 'RFC_IMPORT' => IApiElement::DIRECTION_INPUT, 112 | 'RFC_CHANGING' => IApiElement::DIRECTION_CHANGING, 113 | 'RFC_TABLES' => ITable::DIRECTION_TABLE 114 | ]; 115 | if (!array_key_exists($direction, $mapping)) { 116 | throw new SapLogicException(sprintf('Unknown SAP Netweaver RFC direction \'%s\'!', $direction)); 117 | } 118 | return $mapping[$direction]; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Traits/ConfigTrait.php: -------------------------------------------------------------------------------- 1 | getCommonConfig($config), 34 | $this->getSpecificConfig($config) 35 | ); 36 | } 37 | 38 | /** 39 | * Only type A and B configurations are supported by this module, 40 | * its common classes and its interface. Therefore, we do not 41 | * expect any other types here. 42 | * @param IConfiguration $config 43 | * @return array 44 | * @throws IIncompleteConfigException 45 | */ 46 | private function getSpecificConfig(IConfiguration $config): array 47 | { 48 | if ($config instanceof IConfigTypeA) { 49 | return $this->getTypeAConfig($config); 50 | } 51 | if ($config instanceof IConfigTypeB) { 52 | return $this->getTypeBConfig($config); 53 | } 54 | throw new IncompleteConfigException(sprintf('Unknown config type %s', get_class($config))); 55 | } 56 | 57 | /** 58 | * Get the common configuration for the saprfc module. 59 | * 60 | * I chose a "stupid" (and repetitive) way because it is more readable 61 | * and thus better maintainable for others than an "intelligent" way. 62 | * 63 | * @param IConfiguration $config 64 | * @return array 65 | * @throws IIncompleteConfigException 66 | */ 67 | private function getCommonConfig(IConfiguration $config): array 68 | { 69 | $common = []; 70 | if ($config->getLang() !== null) { 71 | $common['lang'] = $config->getLang(); 72 | } 73 | //mandatory configuration parameters 74 | $common['client'] = $config->getClient(); 75 | $common['user'] = $config->getUser(); 76 | $common['passwd'] = $config->getPasswd(); 77 | return $common; 78 | } 79 | 80 | /** 81 | * Get the connection type A configuration for the saprfc module. 82 | * 83 | * I chose a "stupid" (and repetitive) way because it is more readable 84 | * and thus better maintainable for others than an "intelligent" way. 85 | * 86 | * @param IConfigTypeA $config 87 | * @return array 88 | * @throws IIncompleteConfigException 89 | */ 90 | private function getTypeAConfig(IConfigTypeA $config): array 91 | { 92 | $typeA = []; 93 | if ($config->getGwhost() !== null) { 94 | $typeA['gwhost'] = $config->getGwhost(); 95 | } 96 | if ($config->getGwserv() !== null) { 97 | $typeA['gwserv'] = $config->getGwserv(); 98 | } 99 | //mandatory configuration parameters 100 | $typeA['ashost'] = $config->getAshost(); 101 | $typeA['sysnr'] = $config->getSysnr(); 102 | return $typeA; 103 | } 104 | 105 | /** 106 | * Get the connection type B configuration for the saprfc module. 107 | * 108 | * I chose a "stupid" (and repetitive) way because it is more readable 109 | * and thus better maintainable for others than an "intelligent" way. 110 | * 111 | * @param IConfigTypeB $config 112 | * @return array 113 | * @throws IIncompleteConfigException 114 | */ 115 | private function getTypeBConfig(IConfigTypeB $config): array 116 | { 117 | $typeB = []; 118 | if ($config->getR3name() !== null) { 119 | $typeB['r3name'] = $config->getR3name(); 120 | } 121 | if ($config->getGroup() !== null) { 122 | $typeB['group'] = $config->getGroup(); 123 | } 124 | //mandatory configuration parameter 125 | $typeB['mshost'] = $config->getMshost(); 126 | return $typeB; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Traits/ParamTrait.php: -------------------------------------------------------------------------------- 1 | getName(); 40 | if (array_key_exists($key, $params)) { 41 | $result[$key] = $params[$key]; 42 | } elseif (!$input->isOptional()) { 43 | throw new FunctionCallException(sprintf( 44 | 'Missing parameter \'%s\' for function call \'%s\'!', 45 | $key, 46 | $this->getName() 47 | )); 48 | } 49 | } 50 | return $result; 51 | } 52 | 53 | /** 54 | * Generate a function call parameter array from a list of known tables and the 55 | * previously set parameters. 56 | * @param IApiElement[] $tables 57 | * @param array $params 58 | * @return array 59 | */ 60 | private function getTableParams(array $tables, array $params): array 61 | { 62 | $result = []; 63 | foreach ($tables as $table) { 64 | $key = $table->getName(); 65 | if ( 66 | array_key_exists($key, $params) 67 | && is_array($params[$key]) 68 | && count($params[$key]) > 0 69 | ) { 70 | $result[$key] = $params[$key]; 71 | } 72 | } 73 | return $result; 74 | } 75 | 76 | /** 77 | * @param IApiElement[] $outputs 78 | * @param array $result 79 | * @return array 80 | * @throws IInvalidArgumentException 81 | */ 82 | private function castOutput(array $outputs, array $result): array 83 | { 84 | $return = []; 85 | /** @var Value|Table|Struct $output */ 86 | foreach ($outputs as $output) { 87 | $key = $output->getName(); 88 | if (array_key_exists($key, $result)) { 89 | $return[$key] = $output->cast($result[$key]); 90 | } elseif (!$output->isOptional()) { 91 | throw new FunctionCallException(sprintf( 92 | 'Missing result value \'%s\' for function call \'%s\'!', 93 | $key, 94 | $this->getName() 95 | )); 96 | } 97 | } 98 | return $return; 99 | } 100 | } 101 | --------------------------------------------------------------------------------