Is not reusing created SOAP client object - php

Can anyone tell me why the SOAP client is not being re-used? It keeps getting initialized where it should have been reused from the last call.
When I print out the SOAP client object after it was initialized it is there but it is forgotten at the next call.
So the php script keeps initializing the connection.
My code:
class EcoAPI {
private $client;
public function getClient() {
if (empty($this->client)) {
echo "<br>initializing...";
$this->initClient();
}
return $this->client;
}
private function initClient() {
$settingsOld = Settings::GetOld();
$this->client = new SoapClient("https://api.e-conomic.com/secure/api1/EconomicWebservice.asmx?WSDL", array("trace" => 1, "exceptions" => 1));
$this->client->ConnectWithToken(array('token' => $settingsOld->economic_token_secret, 'appToken' => $settingsOld->economic_token_app));
}
}
I connect by:
$ecoApi = new EcoAPI();
$result = $ecoApi->getClient()->Account_GetVatAccount(array('accountHandle' => (object) array('Number' => (string) $VatAccount)));

Every time you're creating a new EcoAPI object, it won't have any SOAP client information. Using $ecoApi->getClient()->... throughout your code SHOULD work as long as you're using injecting the same $ecoApi object.
If you want to ensure that every EcoAPI object has the same connection object, no matter what, you could try making the client a static property with static methods to manipulate the SOAP object.
class EcoAPI {
static private $client;
static public function getClient() {
if (empty(self::$client)) {
echo "<br>initializing...";
self::initClient();
}
return self::$client;
}
static private function initClient() {
$settingsOld = Settings::GetOld();
self::$client = new SoapClient("https://api.e-conomic.com/secure/api1/EconomicWebservice.asmx?WSDL", array("trace" => 1, "exceptions" => 1));
self::$client->ConnectWithToken(array('token' => $settingsOld->economic_token_secret, 'appToken' => $settingsOld->economic_token_app));
}
}
And connect by:
$result = EcoAPI::getClient()->Account_GetVatAccount(array('accountHandle' => (object) array('Number' => (string) $VatAccount)));
Granted, I would advise against using static objects and methods and it can lead to tight coupling. The best method, in my opinion, is injecting your instantiated objects on an as-needed basis.

Related

How to create soap request and send XML to the WSDL then receive back the response in XML?

I'm creating an integration via API. Basically what I have to do is create soap request and send XML to the WSDL then receive back the response also in XML so I can parse it later.
The problem for me is... how to start because I don't know the soap request rules and it's hard to me to find good resource of knowledge which may help in my case. If you have one, please paste me the link so I can read about it (already checked PHP manual SoapClient but it's magic to me right now).
What I have since now is creating an object via SoapClient class parsing URL to the WSDL:
$soap = new SoapClient('https://wasstt.infomonitor.pl/bigApi/v1/ReportOrderService/WEB-INF/wsdl/wsBigApi1v1.wsdl');
I've also used the __getFunctions() method so i can see the functions inside:
print_r($soap->__getFunctions());
In the result of above code I got an array with functions, here is how it looks:
(
[0] => ResponseMessage addOrder(RequestMessage $addOrderRequest)
[1] => ResponseMessage getOrderList(RequestMessage $getOrderListRequest)
[2] => ResponseMessage getOrderStatus(RequestMessage $getOrderStatusRequest)
[3] => ResponseMessage getReport(RequestMessage $getReportRequest)
[4] => ResponseMessage getStatistic(RequestMessage $getStatisticRequest)
[5] => ResponseMessage checkAppStatus(RequestMessage $checkAppStatusRequest)
[6] => ResponseMessage updateMonitoring(RequestMessage $updateMonitoringRequest)
[7] => ResponseMessage removeTracedEvents(RequestMessage $removeTracedEventsRequest)
[8] => ResponseMessage getMonitoredEntities(RequestMessage $getMonitoredEntitiesRequest)
[9] => ResponseMessage getTracedEvents(RequestMessage $getTracedEventsRequest)
[10] => ResponseMessage whoMonitorsMe(RequestMessage $whoMonitorsMeRequest)
[11] => ResponseMessage updateEconomicInformation(RequestMessage $updateEconomicInformationRequest)
[12] => ResponseMessage updateLiability(RequestMessage $updateLiabilityRequest)
)
In this moment it seems that there is a light in the tunnel because there is a function getReport which I should call, but the problem is how? I know that there is a __soapCall (php manual):
public SoapClient::__soapCall ( string $function_name , array $arguments [, array $options [, mixed $input_headers [, array &$output_headers ]]] ) : mixed
But i can't figure out what and how i should do the next step.
#edited
I've tried in different way and did:
$report = $soap->getReport($xml); //getReport is the valid function
And i received messege below:
cvc-complex-type.2.4.a: Invalid content was found starting with element 'testDataMarker'. One of '{requestId}' is expected.
Soap requests with PHP are actually quite simple if you follow the web standard. The PHP SoapClient class is designed for doing the whole xml stuff for you. If you only work with objects that are derived from the WSDL file, it becomes pretty easy.
You are on the right way. The first step should be that you determine the functions and data types from the WSDL file. You 'Ve done that already with the $client->__getFunctions() and $client->__getTypes() function.
ResponseMessage getReport(RequestMessage $getReportRequest)
This gives the following information. If you want to call the getReport function of the web service, you must use the RequestMessage object in the request and receive the ResponseMessage object as a response. So where do we get this methods? This informations are written in the linked XSD file or you can use the $client->__getTypes() function as well. This will give us the following informations.
struct ResponseMessage {
messageId responseId;
dateTime responseDateTime;
messageId requestId;
dateTime requestDateTime;
string user;
protocolEnum protocol;
string customProtocol;
string data;
}
RequestMessage {
messageId requestId;
dateTime requestDateTime;
protocolEnum protocol;
string customProtocol;
boolean testDataMarker;
string data;
}
We can translate a struct into a PHP class. This class is a so-called value object.
class RequestMessage
{
protected $requestId;
protected $requestDateTime;
protected $protocol;
protected $customProtocol;
protected $testDataMarker;
protected $data;
public function getRequestId(): ?string
{
return $this->requestId;
}
public function setRequestId(?string $requestId): self
{
$this->requestId = $requestId;
return $this;
}
public function getRequestDateTime(): ?string
{
return $this->requestDateTime;
}
public function setRequestDateTime(?string $requestDateTime): self
{
$this->requestDateTime = $requestDateTime;
return $this;
}
public function getProtocol(): ?string
{
return $this->protocol;
}
public function setProtocol(?string $protocol): self
{
$this->protocol = $protocol;
return $this;
}
public function getCustomProtocol(): ?string
{
return $this->customProtocol;
}
public function setCustomProtocol(?string $customProtocol): self
{
$this->customProtocol = $customProtocol;
return $this;
}
public function getTestDataMarker(): ?bool
{
return $this->testDataMarker;
}
public function setTestDataMarker(?bool $testDataMarker): self
{
$this->testDataMarker = $testDataMarker;
return $this;
}
public function getData(): ?string
{
return $this->data;
}
public function setData(?string $data): self
{
$this->data = $data;
return $this;
}
}
class Credentials
{
protected $user;
protected $password;
public function getUser(): ?string
{
return $this->user;
}
public function setUser(?string $user): self
{
$this->user = $user;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(?string $password): self
{
$this->password = $password;
return $this;
}
}
This is a simple PHP value object with getters and setters for the class properties. The properties are exactly the same as stated out in the struct information. You can do this for all structs the $client->__getTypes() function returns. If all types are mapped to PHP value object classes the soap client will even parse a response in a mapped php class. If there 's no mapping to a response type, the soap client will parse the xml response into a stdClass PHP object.
For the getReport example we need the RequestMessage and the Credentials value object.
The next step is initializing the SoapClient class with the right option parameters. After initialization we need to set the credentials header and after that we set the data for our request.
try {
$client = new SoapClient(
'https://wasstt.infomonitor.pl/bigApi/v1/ReportOrderService/WEB-INF/wsdl/wsBigApi1v1.wsdl',
[
'trace' => true,
'exception' => true,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
'soap_version' => SOAP_1_1,
'classmap' => [
'Credentials' => Credentials::class,
'RequestMessage' => RequestMessage::class,
],
]
);
// set the credentials header
$credentials = (new Credentials())
->setUser('user')
->setPassword('password');
$header = new SoapHeader('http://api.big.pl/bigApi/v1/types', 'credentials', $credentials, false);
$client->__setSoapHeaders([ $header ]);
// set the request data
$requestMessage = (new RequestMessage())
->setRequestId(1)
->setRequestDateTime('2020-01-15T15:00:00')
->setProtocol('bimo v1')
->setTestDataMarker(true)
->setData('test');
$result = $client->getReport($requestMessage);
var_dump($result);
} catch (SoapFault $fault) {
var_dump($fault, $client->__getLastRequest(), __LINE__);
}
As you can see, the initialization of the SoapClient class is contained in a try / catch block. This enables you to catch errors. The trace option enables you to get the sent request and the received response xml. The classmap option contains the xsd types that we 've got from the $client->__getTypes() function. The classmap enables the soap client to map the types to your value object classes.
As we know, that the getReport webservice function needs a RequestMessage object as parameter, we use our RequestObject value object class with all the values we want to submit in the request.
The sent xml request looks as follows:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://api.big.pl/bigApi/v1/types">
<SOAP-ENV:Header>
<ns1:credentials>
<user>user</user>
<password>password</password>
</ns1:credentials>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:getReportRequest>
<requestId>1</requestId>
<requestDateTime>2020-01-15T15:00:00</requestDateTime>
<protocol>bimo v1</protocol>
<testDataMarker>true</testDataMarker>
<data>test</data>
</ns1:getReportRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This xml was automatically generated by the SoapClient class. Ultimately, this request results in an authentication error because I do not know your credentials. But now you know, how to send and receive data with php native PHP classes without writing any xml.
Hope this helped you a bit.

How do you turn a Google Services oAuth2 into a Google Ads API oAuth2 access

So I'm working with Google API Client for PHP and I have an OAuth flow that works,
class GoogleClient {
private static $client_id = "1050479587066-f64vq210hc2m15fdj4r77g8ml7jin30d.apps.googleusercontent.com";
private static $client_Secret = "CK8orQfPNpD9UgF0bqNJinVI";
private static $redirect_uri = '/return.php';
private static $access;
private static $client = null;
private static function checkForAccess(){
if(isset(self::$access)){
return true;
}
if(isset($_SESSION['GoogleAuth'])){
self::$access = $_SESSION['GoogleAuth'];
return true;
}
return false;
}
public static function GetClient(){
if(is_null(self::$client)){
$params = [
"client_id" => self::$client_id,
"client_secret" => self::$client_Secret,
"redirect_uri" => self::$redirect_uri,
"application_name" => "Test AdWords System"
];
if(self::checkForAccess() && self::isLoggedIn()){
$param["access_token"] = self::$access['access_token'];
}
//Create and Request to access Google API
$client = new Google_Client($params);
}
return $client;
}
public static function doLogin(){
$scopes = [ 'https://www.googleapis.com/auth/adwords', 'https://www.googleapis.com/auth/dfp', "https://www.googleapis.com/auth/userinfo.email"];
return self::GetClient()->createAuthUrl($scopes);
}
public static function doLoginFinal(){
if (!$code = $_GET['code']) {
throw new Exception("Auth Code is missing.");
}
$authResponse = self::GetClient()->authenticate($code);
if (isset($authResponse['error'])) {
throw new Exception(
"Unable to get access token.",
null,
new Exception(
"{$authResponse['error']} {$authResponse['error_description']}"
)
);
}
$_SESSION['GoogleAuth'] = $authResponse;
self::$access = $authResponse;
}
public static function isLoggedIn(){
if(self::checkForAccess()){
if(isset(self::$access)){
$expiresAt = #self::$access['created']+#self::$access['expires_in'];
return (time() < $expiresAt);
}
}
return false;
}
public static function GetExpiry(){
if(self::checkForAccess()){
return self::$access['created']+self::$access['expires_in'];
}
throw new Exception("The User is not logged into a google account.");
}
}
now this class is working I'm able to log in and I have the scope for google-adwords the problem comes about due to poor documentation for the googleads-php-lib
So from the example to getCampaigns it uses $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build(); but i don't have a file so i went into the OAuth2TokenBuilder file I'm unable to work out how i could give the already generated access tokens to the googleads objects.
I have double checked the google-php-api-client services repo and there is no adwords Service I can use.
I have been digging through the source files of the googleads-php-lib to see if I can find a method to implement this but so far I'm just getting stuck as everything seems to require specific parameter types so I can rig something to provide the details, but the code always seems to rely on multiple classes so I can't just build one that extends a class. and i pass that through.
Keys will be destoried after this test is working!
Well after days of digging around source files and hacking this and that I finally found an implementation that works.
After creating my manager account:
https://developers.google.com/adwords/api/docs/guides/signup
So this is the two new methods added to my GoogleClient Static Class
private static $developerToken = "";
private static function GetUserRefreshCredentials(){
return new UserRefreshCredentials(
null,
[
'client_id' => self::$client_id,
'client_secret' => self::$client_secret,
'refresh_token' => self::$access['refresh_token']
]
);
}
public function GetAdwordsSession(){
$builder = new AdWordsSessionBuilder();
$builder->defaultOptionals();
$builder->withDeveloperToken(slef::$developerToken);
return $builder->withOAuth2Credential(self::GetUserRefreshCredentials())->build();
}

LastRequest is empty, POST is empty. How to get the XML request from Zend Soap Server?

I've implemented a zend soap server with the code below. For my purposes i need to be able to access the full XML message or at the very least its headers. However, the getLastRequest method on SoapServer returns empty, and all the super globals, such as $_GET, $_POST and whatnot, are also empty. Anyone has any ideas?
class SoapTest
{
protected $server;
public function __construct(\Zend\Soap\Server $server)
{
$this->server = $server;
}
/***
* #param string $requestIn
* #return string
*/
public function test($requestIn)
{
// access XML here
}
}
$serverUrl = "http://localhost/SoapTest.php";
$options = [
'uri' => $serverUrl,
];
$server = new Zend\Soap\Server(null, $options);
if (isset($_GET['wsdl'])) {
$soapAutoDiscover = new \Zend\Soap\AutoDiscover(new \Zend\Soap\Wsdl\ComplexTypeStrategy\ArrayOfTypeSequence());
$soapAutoDiscover->setBindingStyle(array('style' => 'document'));
$soapAutoDiscover->setOperationBodyStyle(array('use' => 'literal'));
$soapAutoDiscover->setClass(SoapTest::class);
$soapAutoDiscover->setUri($serverUrl);
header("Content-Type: text/xml");
echo $soapAutoDiscover->generate()->toXml();
} else {
$soap = new \Zend\Soap\Server($serverUrl . '?wsdl', array('cache_wsdl' => WSDL_CACHE_NONE));
$soap->setObject(new \Zend\Soap\Server\DocumentLiteralWrapper(new SoapTest($soap)));
$soap->handle();
}
Apparently, Zend Soap Server fills the $request property (which is returned in getLastRequest) AFTER the handle, so i cant access it in my method.
I can however access the XML by calling the following:
$request = file_get_contents('php://input');

PHPUnit - Mocking a Guzzle / S3 Transfer Class Object With Promise Methods

I have a function that looks like:
public function downloadProjectFolder($projectId, $taskToken){
// Download the project directory if it isn't on the server
if(is_dir('/path/to/folder/') === false){
$manager = $this->instantiateS3TransferObject($projectId, $taskToken);
$promise = $manager->promise();
$promise->wait();
}
else{
return 'Project Folder Already Exists';
}
}
The above method downloads a folder onto my server from AWS S3 if it doesn't already exist on the local machine. The actual S3 Transfer object (from the AWS PHP SDK V3 library - which in itself is mostly abstracted from Guzzle PHP) is instantiated by the below function:
private function instantiateS3TransferObject($projectId, $taskToken){
$lastDatetime = time();
return new \Aws\S3\Transfer($this->myS3ClientObject, 's3://mys3bucket/destination/url',
'/path/to/local/directory/', array(
'base_dir' => 'destination/url',
'before' => function()use($projectId, $taskToken, &$lastDatetime){
$currentDatetime = time();
if(($currentDatetime - $lastDatetime) >= 30){
$postParams = array(
'project_id' => $projectId,
'task_token' => $taskToken
);
$this->curl->post($postParams, 'http://url/endpoint');
$lastDatetime = $currentDatetime;
}
}
)
);
}
The above essentially starts my folder download and hits an custom endpoint every 30 seconds asynchronously.
How would I mock out the \Aws\S3\Transfer object in this case so that it includes the promise() method on return and that method in turn returns the wait() method?
Not much you can do about the time since it is a native function and cannot be mocked. You can slightly refactor it for the sake of testability to something like:
class TimeGetter
{
public function getTime()
{
return time();
}
}
and then use as
$currentDatetime = $this->timeGetter->getTime();
// instead of $currentDatetime = time();
So you can mock it later, and return whatever time you need to test your functionality.
Neither you can create a mock for \Aws\S3\Transfer, since you explicitly create a new instance in instantiateS3TransferObject.
For the rest of the code you will need to mock both Guzzle and curl. The very rough approximation based on the code snippet in the question:
// First Guzzle, but order doesn't matter:
$mock = new MockHandler([
// first expected request
function($_self, RequestInterface $request, array $options) {
// assert $request values meet expectations
// and return response or exception you expect from S3
return new Response(200, ['X-Foo' => 'Bar']);
},
// second expected request, if any, is either instance of Response, Exception, or a function which returns one of them
// third expected request, etc...
]);
$handler = HandlerStack::create($mock);
// then pass it to the class under test
// assuming it is public:
$cut->myS3ClientObject = new Client(['handler' => $handler]);
// Now curl:
$curlMock = $this->getMockBuilder('\whatever\curl\class\you\use')
->disableOriginalConstructor()
->setMethods(['post'])
->getMock();
$curlMock
->expects($this->once()) // twice, exact, etc ...
->method('post')
->with(
$this->equalTo(['project_id' => 'expected_project_id', 'task_token' => 'expected_token' ]),
$this->equalTo('http://url/endpoint')
);
//again, assuming it is public
$cut->curl = $curlMock;
// then actually execute the method you are testing:
$cut-> downloadProjectFolder('expected_project_id', 'expected_token');
You can read more how to test Guzzle in official docs.

Zend soap call for method with hyphen

I rewrite soap client file using Zend framework.
This is old method. it is working.
function getBassaService(){
global $service;
$h="127.0.0.1";
$p="8000";
if($service==null){
$service = new SoapClient("/test/php/bassa.wsdl", array(
"soap_version" => SOAP_1_2,
"trace" => 1,
"exceptions" => 1,
"location" => "http://".$h.":".$p));
}
return $service;
}
function getAllDownloads(){
global $service;
$client = getService();
try{
$results = $client->__soapCall("list-all", array());
}catch(SoapFault $e){
print($e->faultstring);
}
return $result;
}
This is my new code. I use Zend_Soap_Client.
const HOST = "127.0.0.1";
const PORT = "8095";
protected $_client;
public function __construct()
{
$this->_client = new Zend_Soap_Client(APPLICATION_PATH ."/services/bassa.wsdl",
array(
"soap_version" => SOAP_1_2,
"uri" => "http://". self::HOST .":". self::PORT
)
);
}
public function getAllDownloads()
{
$result = $this->_client->list-all();
return $result;
}
My soap server has list-all method. I want soap call to that method. But following error has occurred. Because method name has hyphen.
Notice: Undefined property: Zend_Soap_Client::$list in /home/dinuka/workspace/testzend/application/services/SoapClient.php on line 57
Fatal error: Call to undefined function all() in /home/dinuka/workspace/testzend/application/services/SoapClient.php on line 57
How i fixed it. Please help me.
strange. that's should work. it might be a a bug in ZF framework. maybe it's trying to convert the function name into a camel case function name with variables.
Try to use the magic function directly by calling:
$this->_client->__call('list-all', array('param1' => $param1))

Categories