Skip to content

Tutorial

Pavel Selitskas edited this page Jan 11, 2021 · 12 revisions

On this page you can find a few samples of code using this toolkit.

Namespace \AlexaCRM\WebAPI is assumed by default in text. Code listings try to use fully-qualified class names.

Including Composer autoloader

Before you start using this library, you need to install it via Composer and include the generated autoloader into your application.

require_once 'vendor/autoload.php';

Creating the client instance

The library exposes a public API which includes an IOrganizationService-compatible Client. There are a few options to create a new instance of the client.

Use ClientFactory

ClientFactory provides a simple way to instantiate a new Client with all dependencies in place. As Dynamics 365 (online) is currently the only supported type of deployment, ClientFactory contains only one static method, ClientFactory::createOnlineClient. For IFD and On-Premises support please refer to Roadmap.

The toolkit uses server-to-server authentication and application users supported by Dynamics 365. Read the walkthroughs how to register your application on Azure AD and how to create a Dynamics 365 application user.

The minimal set of values you need to connect to Dynamics 365 Online is as follows:

  • Organization URI, e.g. https://contoso.crm.dynamics.com
  • Application ID - identifies the Azure AD application registration associated with your Dynamics 365 organization
  • Application Secret - a password generated in Settings / API Access / Keys section of your Azure AD application registration

Once you have all three items, it's time to create the client:

$client = \AlexaCRM\WebAPI\ClientFactory::createOnlineClient(
    'https://contoso.crm.dynamics.com',
    '00000000-0000-0000-0000-000000000000',
    'Application Secret'
);

ClientFactory::createOnlineClient() accepts an optional fourth argument. The method expects a map with logger and cachePool keys which hold as values PSR-3 compliant logger and PSR-6 compliant cache adapter correspondingly.

The library follows the lazy initialization pattern and doesn't actually validate the connection at client initialization time. To see how to handle errors, see below.

Manual creation

Sometimes you may want to create all necessary objects yourself. One case when this is useful is when you need to specify extra options in the client settings.

Settings

The first step is to create an OData\Settings object. For online deployments, use OData\OnlineSettings.

$settings = new \AlexaCRM\WebAPI\OData\OnlineSettings();
$settings->instanceURI = 'https://contoso.crm.dynamics.com';
$settings->applicationID = '00000000-0000-0000-0000-000000000000';
$settings->applicationSecret = 'Application Secret';

You can optionally supply a PSR-3 compliant logger.

$settings->setLogger( $logger );

You can optionally supply a PSR-6 compliant cache adapter.

$settings->cachePool = $cacheAdapter;

By default, the HTTP client used by the library verifies SSL certificates against the local CA certificate bundle. If it is broken, like as what happens often on development environments in Windows and OS X, you can either supply a valid CA bundle or disable verification.

$settings->caBundlePath = '/path/to/ca-bundle.crt';
// or
$settings->tlsVerifyPeers = false;

The default Web API version which the plugin connects to is 9.0. It is the minimal tested Web API version as well. (Although not tested, it might very well work with earlier versions, perhaps with some limitations.)

If you need to change the API version, it is also configured in the Settings object:

$settings->apiVersion = '9.1';

Authentication Middleware

The authentication process kicks in when the request is being made. For Online deployments, that means receiving a new access token from Azure AD and putting it into the Authorization header of the request.

$middleware = new \AlexaCRM\WebAPI\OData\OnlineAuthMiddleware( $settings );

If a valid PSR-6 cache adapter is supplied, the token will be cached for the duration of its TTL.

OData Client

Under the hood works a helper object that creates OData-compliant requests to the service endpoint. It consumes the OData\Settings and OData\AuthMiddlewareInterface objects.

$odataClient = new \AlexaCRM\WebAPI\OData\Client( $settings, $middleware );

Web API Client

The Web API Client class is compatible with IOrganizationService from the CRM SDK. To create one, you need to provide the OData client created earlier.

$client = new \AlexaCRM\WebAPI\Client( $odataClient );

After this step, the client is ready to make requests to Dynamics 365 Web API.

Making CRUD requests

Create a record

To create a new record in CRM, make an Entity object and pass it to the Client::Create() method.

$contact = new \AlexaCRM\Xrm\Entity( 'contact' );
$contact['firstname'] = 'Nancy';
$contact['lastname'] = 'Anderson';
$contact['emailaddress1'] = '[email protected]';

$contactId = $client->Create( $contact );

Retrieve a record

To retrieve a record, you need to specify its entity name, entity ID and a column set. Although you can retrieve all columns, it is strongly advised to select only necessary attributes for performance reasons, both at CRM and client side.

$retrievedContact = $client->Retrieve( 'contact', $contactId, new \AlexaCRM\Xrm\ColumnSet( [ 'fullname', 'emailaddress1' ] ) );

To retrieve all columns:

new \AlexaCRM\Xrm\ColumnSet( true )

Retrieve multiple records

Client::RetrieveMultiple() is used to retrieve multiple records from Dynamics 365. It supports FetchExpression and QueryByAttribute. QueryExpression is not currently supported. For advanced OData queries in Web API see below.

Client::RetrieveMultiple() returns an \AlexaCRM\Xrm\EntityCollection instance.

Fetching records via FetchExpression

Querying data with FetchXML is pretty straightforward. Use FetchExpression and supply a stringt with a FetchXML query.

$fetchXML = <<<FETCHXML
<fetch mapping="logical"> 
    <entity name="contact">
        <attribute name="accountid" /> 
        <attribute name="fullname" /> 
        <attribute name="emailaddress1" />
    </entity>
</fetch>
FETCHXML;

$fetchExpression = new \AlexaCRM\Xrm\Query\FetchExpression( $fetchXML );
$collection = $client->RetrieveMultiple( $fetchExpression );

Pagination support

To enable pagination, add the count integer attribute to the fetch element of the query. On consecutive requests, add the page number and the paging cookie to the fetch element as well. MoreRecords in the returned EntityCollection will tell whether there are more pages to retrieve, and PagingCookie will contain the paging cookie string.

$fetchXML = <<<FETCHXML
<fetch mapping="logical" count="10" paging-cookie="%s" page="%d">
    <entity name="contact">
        <attribute name="fullname"/>
        <attribute name="emailaddress1"/>
    </entity>
</fetch>
FETCHXML;

$page = 1;
$query = new \AlexaCRM\Xrm\Query\FetchExpression( sprintf( $fetchXML, '', $page ) );

$result = $client->RetrieveMultiple( $query );
foreach ( $result->Entities as $contact ) {
    printf( "%s <%s>\n", $contact['fullname'], $contact['emailaddress1'] );
}

while ( $result->MoreRecords ) {
    $query->Query = sprintf( $fetchXML, htmlentities( $result->PagingCookie, ENT_COMPAT | ENT_XML1 ), ++$page );
    $result = $client->RetrieveMultiple( $query );
    foreach ( $result->Entities as $contact ) {
        printf( "%s <%s>\n", $contact['fullname'], $contact['emailaddress1'] );
    }
}

Fetching records via QueryByAttribute

$query = new \AlexaCRM\Xrm\Query\QueryByAttribute( 'contact' );
$query->AddAttributeValue( 'lastname', 'Example' );
$query->AddOrder( 'firstname', \AlexaCRM\Xrm\Query\OrderType::Descending() );
$query->ColumnSet = new \AlexaCRM\Xrm\ColumnSet( [ 'fullname', 'emailaddress1' ] );
$collection = $client->RetrieveMultiple( $query );

Pagination support

To enable pagination, specify the PageInfo property of the QueryByAttribute instance with \AlexaCRM\Xrm\Query\PagingInfo as value. On consecutive requests, copy the value of PagingCookie from the returned EntityCollection to the PagingInfo object (PageInfo property of QueryByAttribute). MoreRecords in the returned EntityCollection will tell whether there are more pages to retrieve, and PagingCookie will contain the paging cookie string.

$pagingInfo = new \AlexaCRM\Xrm\Query\PagingInfo();
$pagingInfo->Count = 10;

$query = new QueryByAttribute( 'contact' );
$query->ColumnSet = new \AlexaCRM\Xrm\ColumnSet( [ 'fullname', 'emailaddress1'] );
$query->AddOrder( 'lastname', \AlexaCRM\Xrm\Query\OrderType::Ascending() );
$query->PageInfo = $pagingInfo;

$result = $client->RetrieveMultiple( $query );

foreach ( $result->Entities as $contact ) {
    printf( "%s <%s>\n", $contact['fullname'], $contact['emailaddress1'] );
}
while ( $result->MoreRecords ) {
    $pagingInfo->PagingCookie = $result->PagingCookie;
    $result = $client->RetrieveMultiple( $query );
    foreach ( $result->Entities as $contact ) {
        printf( "%s <%s>\n", $contact['fullname'], $contact['emailaddress1'] );
    }
}

Update a record

$record = new \AlexaCRM\Xrm\Entity( 'contact', '00000000-0000-0000-0000-000000000000' );
$record['emailaddress1'] = '[email protected]';
$client->Update( $record );

Delete a record

$client->Delete( 'contact', '00000000-0000-0000-0000-000000000000' );

Associating records

You can associate/disassociate records in two different ways: in a separate request (Associate and Disassociate) and during Create/Update.

Associate and Disassociate in a separate request

The example given below will associate the account record with three contact records by setting contact[parentcustomerid] lookup (Customer) attribute to the corresponding account lookup value.

$client->Associate(
    'account',
    '00000000-0000-0000-0000-000000000009',
    new \AlexaCRM\Xrm\Relationship( 'contact_customer_accounts' ),
    [
        new \AlexaCRM\Xrm\EntityReference( 'contact', '00000000-0000-0000-0000-000000000001' ),
        new \AlexaCRM\Xrm\EntityReference( 'contact', '00000000-0000-0000-0000-000000000002' ),
        new \AlexaCRM\Xrm\EntityReference( 'contact', '00000000-0000-0000-0000-000000000003' ),
    ]
);

Client::Associate() and Client::Disassociate() have the same signature.

Associate records on create/update

Dynamics 365 Web API allows you making associations between records during create/update request. When you prepare such a request, you need to know the corresponding navigation property for the given entity. In addition, lookup values with multiple targets like Customer lookups, require specifying different navigation properties for different entities (accounts and contacts for the Customer type).

This library does the heavy-lifting for you:

$contact = new \AlexaCRM\Xrm\Entity( 'contact', '00000000-0000-0000-0000-000000000001' );

$contact['parentcustomerid'] = new \AlexaCRM\Xrm\EntityReference( 'account', '00000000-0000-0000-0000-000000000009' );
// or
$contact['parentcustomerid'] = new \AlexaCRM\Xrm\EntityReference( 'contact', '00000000-0000-0000-0000-000000000002' );

$client->Update( $contact );

Disassociate records on update

Similar to associating, you can disassociate records in one-to-many relationships during the update request. Simply set the attribute to NULL to remove the association.

$account = new \AlexaCRM\Xrm\Entity( 'account', '00000000-0000-0000-0000-000000000002' );
$account['primarycontactid'] = null;
$client->Update( $account );

Executing actions and functions

At this point, calling Client::Execute() will throw an exception saying "Execute request not implemented". To execute actions and functions, you will need to use the underlying OData helper client which is accessed via Client::getClient() method.

Actions

Actions can be invoked via OData\Client::executeAction() method. Only the first argument, $actionName, is required. Specify action parameters in the second argument. If the action is bound, third and fourth optional arguments specify the collection name and record ID.

The return value is given without the OData context annotation.

Functions

Functions can be invoked via OData\Client::executeFunction() method. Only the first argument, $functionName, is required. Specify function parameters in the second argument. If the function is bound, third and fourth optional arguments specify the collection name and record ID.

The return value is given without the OData context annotation.

Advanced OData usage

If you want to construct an advanced OData query, you will need to use the HTTP client and query the data directly. The HTTP client is available via OData\Client::getHttpClient(), and OData\Client is available via Client::getClient().

Note: The data retrieved is not deserialized into Entity objects.

Have fun!