erikgall/samsara
Laravel package for integrating Samsara's fleet telematics API into your application, providing a clean, expressive interface for managing vehicles, drivers, and real-time tracking data.
Requirements: PHP 8.2+ and Laravel 12.x (12.0+) or 13.x. Laravel 10, 11, and PHP 8.1 are no longer supported as of v1.1.0 — Composer will refuse to install on those stacks.
Install and configure:
composer require erikgall/samsara
php artisan vendor:publish --tag="samsara-config"
Add your API token to .env (the config file's key name is authoritative — check config/samsara.php after publishing):
SAMSARA_API_TOKEN=your-token-here
Where to look first:
config/samsara.php — token, base URL, timeouts, retry settings.Samsara facade class — its accessor methods tell you which resources exist (vehicles(), drivers(), trips(), tags(), etc.).src/Entity.php — this is the value object every response is hydrated into. Understanding it is the key to everything downstream.First use case — read a resource and iterate it:
use ErikGall\Samsara\Facades\Samsara;
foreach (Samsara::vehicles()->all() as $vehicle) {
dump($vehicle->id, $vehicle->name);
}
Confirm the exact accessor and method names against the facade before building on them; most resources follow a consistent all() / get($id) shape.
Wrap the SDK in your own service layer. Don't let Samsara:: calls leak into controllers or Blade. Create a thin adapter (App\Services\Samsara\FleetService) that calls the SDK and returns your own DTOs or Eloquent models. Samsara's API surface is large and changes; the adapter is where you absorb that.
Map Entity to something you own. Entities are fluent-style read objects, not Eloquent models. Map them at the boundary:
$vehicle = Samsara::vehicles()->get($id);
VehicleRecord::updateOrCreate(
['samsara_id' => $vehicle->id],
['name' => $vehicle->name, 'updated_at' => now()]
);
Sync via queued jobs, not request cycles. Anything beyond a single lookup belongs in a job. Samsara list endpoints are paginated and can be slow:
class SyncVehicles implements ShouldQueue
{
use InteractsWithQueue, Queueable;
public int $tries = 5;
public function backoff(): array
{
return [10, 30, 60, 120];
}
public function handle(FleetService $fleet): void
{
foreach ($fleet->allVehiclesLazy() as $vehicle) {
// upsert
}
}
}
Use a generator/lazy-style loop rather than collecting every page into memory; fleets get large.
Respect rate limits with job middleware. Samsara enforces per-endpoint limits and returns 429 with retry hints. Use Laravel's RateLimited middleware and treat 429/5xx as retryable, 4xx as permanent:
public function middleware(): array
{
return [(new RateLimited('samsara'))->releaseAfter(30)];
}
Define the samsara limiter in a service provider with a conservative perMinute value derived from your account tier.
Inject rather than facade-call inside tests. If the manager resolves from the container, prefer constructor-injecting it (or your own adapter) so you can swap in fakes. Otherwise stub at the HTTP layer:
Http::fake([
'api.samsara.com/*' => Http::response(['data' => [/* ... */]], 200),
]);
Faking HTTP is the most reliable approach because it also exercises pagination and hydration.
Cache slow-moving reference data. Vehicles, tags, and driver rosters change infrequently. A 5–15 minute Cache::remember around list calls cuts API traffic substantially and keeps you clear of rate limits.
Handle Samsara webhooks in a dedicated endpoint. Verify the signature/secret from the Samsara dashboard, respond 200 immediately, and dispatch a job to fetch the canonical record from the API rather than trusting the payload shape.
v1.1.0 is a breaking release for older stacks. Dropping Laravel 10/11 and PHP 8.1 means composer update will fail on those projects — it isn't a soft deprecation. If you share the package across multiple apps, bump each one's laravel/framework and php constraints first, then update. Check your CI matrix too, not just local.
Entity::make() is now local, not Fluent::make(). This is the subtle one. The SDK previously constructed entities via Illuminate\Support\Fluent::make(), which only landed in Laravel 12.9 — so on 12.0–12.8 entities were effectively broken. The new local factory fixes that. The consequence: Entity may no longer be a Fluent instance, or may inherit from it without relying on its static factory. If you wrote Entity::make([...]) in your own code or extended Entity, re-test. If you used Fluent-specific methods on entity objects (->get(), ->set(), ->first(), ->forget()), verify each still exists — prefer plain property access and toArray() / all() for portability.
Laravel 12.0–12.8 is now genuinely supported. If you pinned laravel/framework: ^12.9 solely to work around the Fluent::make() bug, you can relax that constraint. The new --prefer-lowest CI cell exists to keep those lower bounds honest.
Entities are read-oriented. Treat values as immutable once hydrated. Don't persist entity objects into cache or the session — serialize toArray() instead; class shape changes between minor versions are not a breaking-change concern for the maintainer.
Timestamps arrive as strings. Samsara returns RFC3339 UTC. Wrap with Carbon::parse() at your boundary and store as UTC; don't rely on the entity to cast for you.
config:cache will bite you. After changing the token or base URL, run php artisan config:cache (or config:clear in dev). Also note that if you're swapping tokens per-tenant at runtime with config()->set(), an already-cached config still resolves correctly for config() reads — but anything reading env() directly inside the SDK will not.
Debugging outbound calls. The SDK hides the underlying HTTP client, so Http::dd() isn't directly available. Use Laravel's global HTTP middleware to log everything transparently (Laravel 11+):
Http::globalRequestMiddleware(fn ($request) =>
logger()->debug('samsara', ['url' => (string) $request->url()]));
Alternatively, drop a Log::debug inside your adapter around each call — often simpler and scoped to just Samsara traffic.
Pagination limits are per-endpoint. Samsara caps limit differently across resources. Don't hardcode a single page size; read it from config or pass it through your adapter and let the API reject bad values early.
Extension points. If the manager is container-bound, you can bind()/extend() a custom client or adapter — useful for adding correlation IDs, metrics, or a mock transport in tests. Check whether the facade is macroable before reaching for Samsara::macro(); if it isn't, the service-container binding is the supported seam.
How can I help you explore Laravel packages today?