Problem
This is part of a SDK for interacting with Communicator’s SOAP api.
The aim is that the user will not have to interact with SOAP (some people complain about it).
Please note that the wired capitalisation on some properties are intentional and are required for correct conversion to a SOAP request.
I would especially like feedback on the general structure of the package, ie.
- Create service
- Create request
- Call method on service
- Service calls SOAP and provides response to another class to format / validate
Usage example:
use JmsfwkCommunicatorDataServiceGetContactSubscription;
use JmsfwkCommunicatorResourcesCommunicatorCredentials;
use JmsfwkCommunicatorResourcesSubscriptionInfo;
use JmsfwkCommunicatorServicesDataService;
$credentials = new CommunicatorCredentials('username', 'password');
$dataService = new DataService($credentials);
$request = new GetContactSubscription('adam@example.com', 12345);
//try {...
$subscriptionInfo = $dataService->getContactSubscription($request);
Service.php
abstract class Service
{
const NS = 'http://ws.communicatorcorp.com/';
/**
* @var SoapClient
*/
protected $client;
/**
* @var CommunicatorCredentials
*/
protected $credentials;
/**
* Service constructor.
*
* @param CommunicatorCredentials $credentials
* @param SoapClient $client
*/
public function __construct(CommunicatorCredentials $credentials,
SoapClient $client = null)
{
$this->credentials = $credentials;
if ($client === null) {
$client = self::makeClient();
}
$this->client = $client;
$header = new SoapHeader(self::NS, 'CommunicatorCredentials',
$credentials->getHeader());
$this->client->__setSoapHeaders($header);
}
public static function makeClient(array $soapSettings = []): SoapClient
{
$defaults = [
'trace' => false,
'exception' => true,
'soap_version' => SOAP_1_2,
];
return new SoapClient(static::getWSDL(), array_merge($defaults,
$soapSettings));
}
abstract protected static function getWSDL(): string;
}
DataService.php
namespace JmsfwkCommunicatorServices;
use JmsfwkCommunicatorDataServiceGetContactSubscription;
use JmsfwkCommunicatorResourcesSubscriptionInfo;
use SoapClient;
class DataService extends Service
{
const WSDL = 'https://ws.communicatorcorp.com/DataService.asmx?WSDL';
public function getContactSubscription(
GetContactSubscription $request): SubscriptionInfo
{
$response = $this->client->GetContactSubscription($request);
return GetContactSubscription::formatResponse(
$response->GetContactSubscriptionResult);
}
public static function makeClient(array $soapSettings = []): SoapClient
{
$defaults = [
'trace' => true,
'exception' => true,
'soap_version' => SOAP_1_2,
];
return new SoapClient(self::WSDL, array_merge($defaults, $soapSettings));
}
}
CommunicatorCredentials.php
namespace JmsfwkCommunicatorResources;
use SoapHeader;
use SoapVar;
class CommunicatorCredentials extends Resource
{
/** Communicator's namespace */
const NAME_SPACE = 'http://ws.communicatorcorp.com/';
/** The name of the XML node in the SOAP request */
const HEADER_NAME = 'CommunicatorCredentials';
/** @var string */
public $Username;
/** @var string */
public $Password;
/**
* @param string $username
* @param string $password
*/
public function __construct(string $username, string $password)
{
$this->Username = $username;
$this->Password = $password;
}
/**
* Build a SoapHeader for authenticating with Communicator.
*
* Communicator requires that the authentication header is built of SoapVar's
* instead of an object matching the required fields.
*
* @return SoapHeader
*/
public function getHeader(): SoapHeader
{
$data = new SoapVar(
[
new SoapVar($this->Username, XSD_STRING, null,
null, 'Username', self::NAME_SPACE),
new SoapVar($this->Password, XSD_STRING, null,
null, 'Password', self::NAME_SPACE),
],
SOAP_ENC_OBJECT
);
return new SoapHeader(self::NAME_SPACE, self::HEADER_NAME, $data);
}
}
GetContactSubscription.php
namespace JmsfwkCommunicatorDataService;
use JmsfwkCommunicatorExceptionsBadResponseData;
use JmsfwkCommunicatorResourcesResource;
use JmsfwkCommunicatorResourcesSubscriptionInfo;
use TypeError;
use UnexpectedValueException;
class GetContactSubscription extends Resource
{
protected $emailAddress;
protected $mailingListId;
public function __construct(string $emailAddress, int $mailingListId)
{
$this->emailAddress = $emailAddress;
$this->mailingListId = $mailingListId;
}
public static function formatResponse(
stdClass $response): SubscriptionInfo
{
try {
return new SubscriptionInfo(
$response->MailingListId,
$response->IsSubscribed,
new SubscriptionSourceType($response->SubscriptionSourceType),
DateTime::createFromFormat("Y-m-dTH:i:s",
$response->DateLastAction)
);
} catch (UnexpectedValueException $e) {
throw new BadResponseData($e->getMessage(), $e->getCode(), $e);
} catch (TypeError $e) {
throw new BadResponseData($e->getMessage(), $e->getCode(), $e);
}
}
}
SubscriptionInfo.php
namespace JmsfwkCommunicatorResources;
class SubscriptionInfo extends Resource
{
public $MailingListId;
public $IsSubscribed;
public $DateLastAction;
public $SubscriptionSourceType = 'TestCase';
public function __construct($MailingListId, $IsSubscribed = true)
{
$this->MailingListId = $MailingListId;
$this->IsSubscribed = $IsSubscribed;
$this->DateLastAction = date("Y-m-dTH:i:s");
}
}
Solution
If I understand well your question, you want to have feedback about the SDK you’re writing to interact with the SOAP Web service provided by Communicator.
If so, I strongly advise you to take a look to existing WSDL to php generator project such as PackageGenerator because it does what you’re doing generically and fully based on the WSDL of the SOAP Web Service. As you’re doing manually, it generates the methods matching the operations provided by the SOAP Web Service. In addition, it generates all the classes that represent the data that have to be sent to the operations.
Finally, you could then integrate the auto-generated SDK in a high level SDK of you own that would provide easy to use methods to effectively abstract the SOAP interaction.