Using Typo3 eID for nusoap call

292 Views Asked by At

i'm using the following code for my soap call.

If i add the wsdl and make my client call i just get the response without the whole soap wrap.

declare(strict_types=1);
namespace Vendor\DocBasics\Controller;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use Vendor\DocBasics\Domain\Repository\EventsRepository;
use Vendor\CartExtended\Domain\Repository\Order\ItemRepository;

require_once(PATH_site . 'typo3conf/ext/doc_basics/Classes/Libs/nusoap/nusoap.php');

class EventsController
{
protected $action = '';
protected $order;
protected $Vbeln = '';
protected $Zaehl = '';
protected $objectManager;

/**
 * @var array
 */
protected $responseArray = [
    'hasErrors' => false,
    'message' => 'Nothing to declare'
];

/**
 * @param ServerRequestInterface $request
 * @param ResponseInterface $response
 * @return ResponseInterface
 */
public function processRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
    $this->initializeData(file_get_contents('php://input')); //xml datas from soap call

    switch (isset($request->getQueryParams()['action']) ? (string)$request->getQueryParams()['action'] : '') {
        case 'create':
            $this->createAction();
            break;
        case 'update':
            $this->updateAction();
            break;
        default:
            $this->updateAction(); //call it as default, so i can call it as endpoint without action parameter
    }
    $this->prepareResponse($response,$request->getQueryParams()['action']);
    return $response;
}

/**
 * action create
 *
 * @return void
 */
public function createAction()
{

    $server = new \soap_server();
    $server->configureWSDL("updateorderservice", "https://domain.tld/updateorderservice", "https://domain.tld/index.php?eID=update_order");
    $server->register(
        "update",
        array("Vbeln" => 'xsd:string', "Zaehl" => 'xsd:integer'),
        array("return" => 'xsd:string'),
        "https://domain.tld/updateorderservice",
        "update",
        "rpc",
        "encoded",
        "Update a given order"
    );

    $this->responseArray['message']= $server->service(file_get_contents('php://input'));

}

public function updateAction() 
{
    $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
    $this->itemRepository = $this->objectManager->get(ItemRepository::class);

    $order=$this->itemRepository->findOrderByOrder($this->Vbeln);

    if($order){
        $order->setCancelDate($this->Veindat);
        $this->itemRepository->update($order);
        $this->persistenceManager->persistAll();
        $msg= '<MESSAGE><TYPE>S</TYPE><MSGTXT>Auftrag '.$this->Vbeln.' aktualisiert!</MSGTXT></MESSAGE>';
    }
    else $msg= '<MESSAGE><TYPE>E</TYPE><MSGTXT>Auftrag '.$this->Vbeln.' konnte nicht aktualisiert!</MSGTXT></MESSAGE>';

    $this->responseArray['message'] = $msg; //receive the message but don't know how to wrap it
}

/**
 * @param ResponseInterface $response
 * @param String $action
 * @return void
 */
protected function prepareResponse(ResponseInterface &$response, $action)
{
    if($action=='create'){
        $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
        $response->getBody()->write($this->responseArray['message']);
    }
    else{
        $response = $response->withHeader('Content-Type', 'text/xml; charset=utf-8'); 
        $response->getBody()->write($this->responseArray['message']);
    }
}

/**
 * @param  $request
 * @return void
 */
protected function initializeData($request)
{
    $resp= $this->parseResult($request);
    if($resp->Vbeln[0]) $this->Vbeln  = (string)($resp->Vbeln[0]);
    if($resp->Zaehl[0]) $this->Zaehl  = intval($resp->Zaehl[0]);
}

public function parseResult($result){
    $result = str_ireplace(['soapenv:','soap:','upd:'], '', $result);
    $result = simplexml_load_string($result);
    $notification = $result->Body->Update;
    return $notification;
}
 }

My response is just the small xml i'm writing as return to the updateAction(). My response should be wrapped between and so on May be i'm missing something or the way i'm using the eID concept is wrong.

2

There are 2 best solutions below

2
Artur Cichosz On

your case makes much more sense here, than on facebook, but in your future posts on stackoverflow you should write more background information for all the other devs who have no background information as I have.

In general: You overcomplicate things. :-)

First, you told me on facebook, That your soap server as such (without TYPO3 integration as eID) works. Is it so? I can not see that from your code :-) You process some control http parameter "action" and create the SOAP server only if the value is "create". But for the "action" value "update", there is no server initialization? How can that work? You must remember, that a SOAP server must be initialized on each request. It is not a deamon, which gets started once and runs in the background.

There is absolute no need for such an "action" control parameter on the input side. This is what a "SOAP remote method" registration of the NuSOAP server is for - a method with a distinguished name, which you call explicitelly on the client side.

Then your parseResult and parseResponse methods? Are you trying to handle the SOAP protocol manually? NuSOAP is supposed to handle all that for you. You just have to register appropriate data types (ComplexType).

You need to get much more background knowledge on NuSOAP itself first.

Here is a simple working example I used in a very old project. I reduced it to show you how NuSOAP is supposed to work.

The server defines one single Method "echoStringArray", which takes one array as attribute named "inputStringArray" and echoes it back without any modifications.

You can take and copy paste without modifications into your eID script and so you will have immediate basic TYPO3 integration. Then add other things one by one, such as database layer and so on. Try not to use classes first, but the same procedural approach from my example.

So here is the server definition soap-server.php:

<?php

// Pull in the NuSOAP code
require_once('./nusoap-0.9.5/lib/nusoap.php');

function logRequest($userAgent, $methodName, $request, $response, $result) {
    $fp = fopen("./soap.log","a+");
    fputs($fp,"$userAgent\n$methodName\n$request\n$response\n$result\n=======================================\n");
    fclose($fp);
}

$log = true;

// Create the server instance
$SOAP_server = new soap_server;
$SOAP_server->configureWSDL(
    'Test Service',
    'http://my-soap-server.local/xsd'
);

// Set schema target namespace
$SOAP_server->wsdl->schemaTargetNamespace = 'http://my-soap-server/xsd';

// Define SOAP-Types which we will need. In this case a simple array with strings
$SOAP_server->wsdl->addComplexType(
    'ArrayOfstring',
    'complexType',
    'array',
    '',
    'SOAP-ENC:Array',
    array(),
    array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]')),
    'xsd:string'
);

// Define SOAP endpoints (remote methods)

$SOAP_server->register(
    'echoStringArray', // this is the name of the remote method and the handler identifier below at the same time
    array('inputStringArray'=>'tns:ArrayOfstring'),
    array('return'=>'tns:ArrayOfstring'),
    'http://soapinterop.org/'
);

// Define SOAP method handlers

// This is the handler for the registered echoStringArray SOAP method. It just receives an array with strings and echoes it back unmodified
function echoStringArray($inputStringArray){
    $outputData = $inputStringArray;
    return $outputData;
}

// Now let the SOAP service work on the request
$SOAP_server->service(file_get_contents("php://input"));

if(isset($log) and $log == true){
    logRequest($SOAP_server->headers['User-Agent'],$SOAP_server->methodname,$SOAP_server->request,$SOAP_server->response,$SOAP_server->result);
}

And here is the appropriate client soap-client.php:

<?php
require_once('./nusoap-0.9.5/lib/nusoap.php');

// This is your Web service server WSDL URL address
$wsdl = "http://my-soap-server.local/soap-server.php?wsdl";

// Create client object
$client = new nusoap_client($wsdl, 'wsdl');
$err = $client->getError();
if ($err) {
    // Display the error
    echo '<h2>Constructor error</h2>' . $err;
    // At this point, you know the call that follows will fail
    exit();
}

// Call the hello method
$result1 = $client->call('echoStringArray', ['inputStringArray' => ['Hello', 'World', '!']]);

print_r($result1);

As you can see there is absolutely no custom handling of the message body, XML, headers and so on. All this is being taken care for by NuSOAP itself.

You just provide an array under the key inputStringArray in $client->call() and get the same array on the server side as parameter named inputStringArray of the method handler echoStringArray.

And last but not least, you may try something more recent than nuSOAP, e.g. zend-soap. It seems to be simpler, check out this short tutorial https://odan.github.io/2017/11/20/implementing-a-soap-api-with-php-7.html

1
Pierre On

YES! now it works. Ur last remark was the point: "SOAP Server must be initialized on each request". Tought this server initialisation was only use for creating the wsdl. The other difficulty i had was how to call my function. If the function is in the same class it'll not get call (probably due to some autoload issues), i had to make another class with the function to get things working. Here is my whole solution. in ext_localconf.php

$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['update_order'] = Vendor\DocBasics\Controller\EventsController::class . '::processRequest';

My class EventsController

<?php

declare(strict_types=1);
namespace Vendor\DocBasics\Controller;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;

require_once(PATH_site . 'typo3conf/ext/doc_basics/Classes/Libs/nusoap/nusoap.php');
require_once(PATH_site . 'typo3conf/ext/doc_basics/Classes/Libs/Utility.php');

class EventsController
{

protected $objectManager;

/**
* @var array
*/
protected $responseArray = [
'hasErrors' => false,
'message' => 'Nothing to declare'
];

/**
 * @param ServerRequestInterface $request
 * @param ResponseInterface $response
 * @return ResponseInterface
 */
public function processRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{

$server = new \soap_server();
    $server->soap_defencoding='utf-8';
    $server->configureWSDL("updateorderservice", "https://domain.tld/updateorderservice", "https://domain.tld/index.php?eID=update_order");
    $server->register(
        "Utility.updateOrder",
        array("Vbeln" => 'xsd:string', "Zaehl" => 'xsd:integer'),
        array("return" => 'xsd:string'),
        "https://domain.tld/updateorderservice",
        "update",
        "rpc",
        "encoded",
        "Update a given order"
    );

$this->prepareResponse($response);
return $response;
}


 /**
* @param ResponseInterface $response
* @param String $action
* @return void
*/
protected function prepareResponse(ResponseInterface &$response)
{
    $response = $response->withHeader('Content-Type', 'text/xml; charset=utf-8'); 
    $response->getBody()->write($this->responseArray['message']);
}

}

And my class Utility

class Utility
{
public function updateOrder($Vbeln,$Zaehl) 
{
//do ur stuff
 return "Order ".$Vbeln." done";
}
}

U can call ur wsdl with https://domain.tld/index.php?eID=update_order&wsdl Thanks again Artur for helping me solving this. Dziekuje ;-)