Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Dynamics Client Laravel Package

justbetter/laravel-dynamics-client

A Laravel package that provides a fluent client for Microsoft Dynamics, making it easy to query, create and update records from your app. Includes configurable connections, authentication and a clean, Eloquent-like interface for working with Dynamics entities.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require justbetter/laravel-dynamics-client and publish the config: php artisan vendor:publish --tag=dynamics-config. Everything is driven from config/dynamics.php, so open that file first — it defines the named connections (client id/secret, tenant, environment, company, base URL) that every other call hangs off.
  • Put credentials in .env rather than in the config file. You need an Azure AD app registration with client-credentials access to the Dynamics 365 Business Central API, plus the target environment and company name. If the company name has spaces or punctuation, verify early that it resolves — that is a common first-run failure.
  • The package is registered as a facade, so the fastest sanity check in php artisan tinker is a parameterless read against a well-known endpoint (e.g. customers or companies). If you get a token and a JSON collection back, auth and routing are correct; if you get a 401 the credentials/tenant are wrong, if you get a 404 the environment or company segment is wrong.
  • First real use case: a scheduled command that pulls a paged list from Business Central into a local table, so downstream code never talks to Dynamics synchronously. Build that before touching writes — reads are idempotent and easy to re-run while you learn the API's filtering and paging behaviour.

Implementation Patterns

  • Wrap the client, don't sprinkle the facade. Create a DynamicsRepository/service per resource (CustomerRepository, ItemRepository) that owns the endpoint name and the field mapping. Call sites then depend on your class, which you can fake in tests without booting HTTP.
  • One connection per company/environment. The config supports multiple named connections; use production, sandbox, and per-entity-company connections explicitly rather than relying on the default. This makes it impossible for a queued job to write to the wrong environment because an env var was stale.
  • Mirror locally, then reconcile. For anything reporting-related, run php artisan dynamics:sync-customers on a schedule, upsert into Eloquent models keyed by the Dynamics primary key, and keep a synced_at column. Reconcile with incremental filters (modifiedOn gt <last sync>) instead of full pulls once volumes grow.
  • Model-style access for CRUD-heavy code. Where the package exposes model classes for a resource, extend one per entity and define the connection, resource name, primary key, and casts. That gives you Eloquent-like reads in application code while the package handles OData serialisation.
  • Push writes through jobs. Wrap create/update/delete in queued jobs with $tries, backoff(), and failed() handling. Dynamics will rate-limit and occasionally 5xx; a synchronous write in an HTTP request is a bad experience waiting to happen. Log the Dynamics record id/etag on success and store it on your local record for future updates.
  • Handle paging explicitly. Server-driven @odata.nextLink means "get all" is a loop. Encapsulate it in one cursor()/each() helper in your repository and use $top/$skip deliberately; never let a controller call an unbounded fetch.
  • Test against the HTTP layer, not the network. Use Laravel's Http::fake() (or the package's provided fake/binding) with captured fixtures for a list response, an empty response, and an OData error payload. Assert your mapping and your error handling separately — the second is what breaks in production.

Gotchas and Tips

  • Bump to 2.1.0 if you saw unexplained 400s. Prior to this release, requests with no parameters serialised an empty JSON object ({}) as the body. Some endpoints, proxies, and gateways reject that. The fix is a patch-level change with no API impact, so upgrading from 2.0.0 is safe — but note it changes what goes over the wire, so if you have HTTP-level tests asserting on an empty body, update the fixtures.
  • OData filter syntax is a string language, not an array. Quote strings with single quotes and escape embedded quotes by doubling them (name eq 'O''Brien'). Dates want ISO-8601 without timezone surprises. Build filters through a small helper rather than string concatenation at call sites — this is the number-one source of silent "zero results" bugs.
  • URL encoding bites on company names and resource paths. My Company Ltd must be encoded in the path segment. When something returns 404 that works in Postman, check encoding before checking permissions.
  • Tokens are cached; clock skew is not. Long-running queue workers can hold a token past expiry or fail validation after a host clock drift. If you see intermittent 401s only in workers, restart them on deploy and check the token cache TTL config rather than assuming bad credentials.
  • Writes are not transactional across calls. There is no distributed transaction between your DB and Dynamics. Persist an intent record before the call and mark it confirmed after; that gives you something to reconcile and retry rather than a lost update.
  • Throttling is real. Expect 429s under batch load. Respect Retry-After, keep concurrency low, and don't parallelise blindly — sequential with a small delay often finishes faster than a burst that gets throttled.
  • Debugging tip: register a global HTTP client middleware (or listen to Illuminate\Http\Client\Events) to log method, URL, status, and the Dynamics correlation/request id. The request id is what Microsoft support asks for, and it is not in your application logs by default. Redact the Authorization header.
  • Extension points: the config file is the intended customisation surface (connections, HTTP client options, timeouts). Prefer a custom connection entry over subclassing internals; for anything the package does not expose, bind your own service around the facade instead of forking.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource