This package will connect you to your Microsoft Dynamics web services via OData. Custom web services can easily be implemented and mapped to your liking. It uses the HTTP client of Laravel which means that you can easily fake requests when writing tests.
The way we interact with OData has been inspired by Laravel's Query Builder.
$customer = Customer::query()->findOrFail('1000');
$customer->update([
'Name' => 'John Doe',
]);
$customers = Customer::query()
->where('City', '=', 'Alkmaar')
->lazy();
$items = Item::query()
->whereIn('No', ['1000', '2000'])
->get();
$customer = Customer::new()->create([
'Name' => 'Jane Doe',
]);
Install the composer package.
composer require justbetter/laravel-dynamics-client
Publish the configuration of the package.
php artisan vendor:publish --provider="JustBetter\DynamicsClient\ServiceProvider" --tag=config
Add your Dynamics credentials in the .env
:
DYNAMICS_BASE_URL=https://127.0.0.1:7048/DYNAMICS
DYNAMICS_VERSION=ODataV4
DYNAMICS_COMPANY=
DYNAMICS_USERNAME=
DYNAMICS_PASSWORD=
DYNAMICS_PAGE_SIZE=1000
Be sure the DYNAMICS_PAGE_SIZE
is set equally to the Max Page Size
under OData Services
in the configuration of
Dynamics. This is crucial for the functionalities of the lazy
method of the QueryBuilder
.
Note: Be sure that Dynamics has been properly configured for OData.
This package uses NTLM authentication by default. If you are required to use basic auth or OAuth you can change this in
your .env
.
DYNAMICS_AUTH=basic
To setup OAuth add the following to your .env
DYNAMICS_AUTH=oauth
DYNAMICS_OAUTH_CLIENT_ID=
DYNAMICS_OAUTH_CLIENT_SECRET=
DYNAMICS_OAUTH_REDIRECT_URI=
DYNAMICS_OAUTH_SCOPE=
When using D365 cloud with Microsoft identity platform your redirect uri will be: https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
and your base url should be https://api.businesscentral.dynamics.com/v2.0/<tenant>/<environment>
.
Multiple connections are supported. You can easily update your dynamics
configuration to add as many connections as
you wish.
// Will use the default connection.
Customer::query()->first();
// Uses the supplied connection.
Customer::query('other_connection')->first();
By default, the client will use the company
field to select the Dynamics company.
If you wish to use the company's UUID you can simply add an uuid
field:
'Company' => [
'base_url' => env('DYNAMICS_BASE_URL'),
'version' => env('DYNAMICS_VERSION', 'ODataV4'),
'company' => 'Company Name',
'uuid' => 'Company UUID', // The UUID will be prioritized over the company name
Adding a web service to your configuration is easily done. Start by creating your own resource class to map te data to.
use JustBetter\DynamicsClient\OData\BaseResource;
class Customer extends BaseResource
{
//
}
By default, the primary key of a resource will default to No
as a string. You can override this by supplying the
variable $primaryKey
.
public array $primaryKey = [
'Code',
];
Fields in resources will by default be treated as a string. For some fields, like a line number, this should be casted to an integer.
public array $casts = [
'Line_No' => 'int',
];
Lastly, you should register your resource in your configuration file to let the package know where the web service is located. This should correspond to the service name configured in Dynamics.
If your resource class name is the same as the service name, no manual configuration is needed.
Note: Make sure your web service is published.
return [
/* Resource Configuration */
'resources' => [
Customer::class => 'CustomerCard',
],
];
Querying data is easily done using the QueryBuilder.
Using the get
method will only return the first result page. If you wish to efficiently loop through all records,
use lazy
instead.
$customers = Customer::query()
->where('City', '=', 'Alkmaar')
->lazy()
->each(function(Customer $customer): void {
//
});
See the QueryBuilder
class for all available methods.
Any relations published on a page can be accessed as well using the resource.
$salesOrder = SalesOrder::query()->first();
// Get the lines via the "relation" method.
$salesLines = $salesOrder->relation('Relation_Name', SalesLine::class)->get();
// Or use the "lines" helper on the SalesOrder.
$salesLines = $salesOrder->lines('Relation_Name')->get();
Note that the relation
method itself returns an instance of a query builder. This means that you can add additional where-clauses like you would be able to on a regular resource.
Create a new record.
Customer::new()->create([
'Name' => 'John Doe'
])
Update an existing record.
$customer = Customer::query()->find('1000');
$customer->update([
'Name' => 'John Doe',
]);
Delete a record.
$customer = Customer::query()->find('1000');
$customer->delete();
If you wish to review your query before you sent it, you may want to use the dd
function on the builder.
Customer::query()
->where('City', '=', 'Alkmaar')
->whereIn('No', ['1000', '2000'])
->dd();
// Customer?$filter=City eq 'Alkmaar' and (No eq '1000' or No eq '2000')
You can run the following command to check if you can successfully connect to Dynamics.
php artisan dynamics:connect {connection?}
If needed, it is possible to extend the provided ClientFactory
class by creating your own. You must implement the ClientFactoryContract
interface and its methods.
use JustBetter\DynamicsClient\Exceptions\DynamicsException;
use JustBetter\DynamicsClient\Contracts\ClientFactoryContract;
class MyCustomClientFactory implements ClientFactoryContract
{
public function __construct(public string $connection)
{
$config = config('dynamics.connections.'.$connection);
if (! $config) {
throw new DynamicsException(
__('Connection ":connection" does not exist', ['connection' => $connection])
);
}
$this
->header('Authorization', 'Bearer ' . $config['access_token'])
->header('Accept', 'application/json')
->header('Content-Type', 'application/json');
}
...
}
You will then need to bind your custom factory as the implementation of the contract, in any of your ServiceProvider
register method :
<?php
use JustBetter\DynamicsClient\Contracts\ClientFactoryContract;
class AppServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->bind(ClientFactoryContract::class, MyCustomClientFactory::class);
}
}
When writing tests you may find yourself in the need of faking a request to Dynamics. Luckily, this packages uses the HTTP client of Laravel to make this very easy.
In order to fake all requests to Dynamics, you can call the method fake
on any resource.
The
fake
method will fake all requests to Dynamics, not just the endpoint of the used resource.
<?php
use JustBetter\DynamicsClient\OData\BaseResource;
BaseResource::fake();
This method will fake the Dynamics configuration and removes sensitive information like usernames and passwords. Only the company name will remain in order to easily test with multiple connections.
<?php
use Illuminate\Support\Facades\Http;
use JustBetter\DynamicsClient\OData\Pages\Item;
Item::fake();
Http::fake([
'dynamics/ODataV4/Company(\'default\')/Item?$top=1' => Http::response([
'value' => [
[
'@odata.etag' => '::etag::',
'No' => '::no::',
'Description' => '::description::',
],
],
]),
]);
$item = Item::query()->first();
This client can prevent requests from going to Dynamics when it is giving HTTP status codes 503, 504 or timeouts. This can be configured per connection in the availability
settings. Enable the throw
option to prevent any requests from going to Dynamics.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.