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 serviceCreate requestCall method on serviceService calls SOAP and provides response to another class to format / validateUsage 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('adam@example.com', 12345);//try {...   $subscriptionInfo = $dataService->getContactSubscription($request);Service.phpabstract 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.phpnamespace 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.phpnamespace 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.phpnamespace 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.phpnamespace 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);    }}
PHP Communicator SOAP api SDK
php;api;soap