0
\$\begingroup\$

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.

  1. Create service
  2. Create request
  3. Call method on service
  4. Service calls SOAP and provides response to another class to format / validate

Usage example:

use Jmsfwk\Communicator\DataService\GetContactSubscription;
use Jmsfwk\Communicator\Resources\CommunicatorCredentials;
use Jmsfwk\Communicator\Resources\SubscriptionInfo;
use Jmsfwk\Communicator\Services\DataService;

$credentials = new CommunicatorCredentials('username', 'password');
$dataService = new DataService($credentials);
$request = new GetContactSubscription('[email protected]', 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 Jmsfwk\Communicator\Services;

use Jmsfwk\Communicator\DataService\GetContactSubscription;
use Jmsfwk\Communicator\Resources\SubscriptionInfo;
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 Jmsfwk\Communicator\Resources;

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 Jmsfwk\Communicator\DataService;

use Jmsfwk\Communicator\Exceptions\BadResponseData;
use Jmsfwk\Communicator\Resources\Resource;
use Jmsfwk\Communicator\Resources\SubscriptionInfo;
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-d\TH: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 Jmsfwk\Communicator\Resources;

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-d\TH:i:s");
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.