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

Datacenter Laravel Sdk Laravel Package

velocity-sports-labs/datacenter-laravel-sdk

A Laravel SDK for integrating the Velocity Sports Labs Data Center API into PHP applications, with a framework-native client, config publishing, and service provider bindings to simplify authentication and data retrieval.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require velocity-sports-labs/datacenter-laravel-sdk
php artisan vendor:publish --tag="datacenter-config"

Set credentials in .env before the first call — assume DATACENTER_API_KEY and DATACENTER_BASE_URL until you confirm the keys in config/datacenter.php:

DATACENTER_API_KEY=...
DATACENTER_BASE_URL=https://api.velocitysportslabs.com/v1

Where to look first (in this order)

  1. config/datacenter.php — base URL, key name, timeout, and any resource/version switch. This is the contract you tune first.
  2. src/*ServiceProvider.php — shows whether the client is bound as a singleton (it almost certainly is), what facade alias exists, and what is configurable at boot.
  3. src/ root for the manager/client class and src/Resources/ (or src/Endpoints/) — each file there is roughly one API surface (teams, athletes, events, sessions, stats).
  4. src/Data/ or src/DTO/ — the hydration shape. Read one DTO end-to-end before writing any sync logic; it tells you nullability and date handling.
  5. tests/ — with 0 stars and 0 dependents, the test suite is your best (and sometimes only) documentation of expected payloads.

First use case: read-only smoke test

Do not start with a write/sync. Start with a single authenticated GET through the facade in php artisan tinker and dump() the raw response:

use VelocitySportsLabs\DataCenter\Facades\DataCenter;

$page = DataCenter::teams()->list(['per_page' => 5]);
dump($page->toArray());   // inspect the real envelope before trusting any DTO

If the facade alias isn't auto-registered, resolve from the container instead: app(\VelocitySportsLabs\DataCenter\DataCenter::class). Once that returns data, you know auth, base URL, and TLS are correct — the three things that account for most first-day failures.

Implementation Patterns

Inject the client, don't sprinkle the facade

Facades are fine in tinker and one-off commands, but bind the SDK client into your own repository so jobs, tests, and future package swaps stay cheap:

final class DataCenterTeamRepository
{
    public function __construct(private \VelocitySportsLabs\DataCenter\DataCenter $client) {}

    public function all(): array
    {
        return $this->client->teams()->list(['per_page' => 100])->toArray();
    }
}

Then $this->app->bind(DataCenterTeamRepository::class) and let Laravel inject it. When the SDK goes 0.x → 1.0 and method names shift, you change one class.

Sync as queued jobs with idempotent upserts

The day-to-day use of a sports data center is ingestion, not request/response in a controller. The pattern that holds up:

final class SyncTeams implements ShouldQueue
{
    use Queueable;

    public function handle(DataCenterTeamRepository $teams): void
    {
        $rows = collect($teams->all())->map(fn ($t) => [
            'external_id' => $t['id'],
            'name'        => $t['name'],
            'updated_at'  => now(),
        ]);

        Team::upsert($rows->all(), ['external_id'], ['name', 'updated_at']);
    }
}

Key on the upstream id as external_id with a unique index, and always upsert — sports feeds re-send records constantly. Never create() from an API payload.

Paginate explicitly and cap the loop

Sports APIs always paginate. Whether the SDK exposes a lazy cursor or a meta/links envelope, write the loop defensively so a bad next value can't spin forever:

$page = 1;
do {
    $response = DataCenter::athletes()->list(['page' => $page, 'per_page' => 100]);
    $items = $response->data();
    // ... persist
    $hasMore = $response->hasMorePages(); // verify method name in the response class
    $page++;
} while ($hasMore && $page <= 50);

If there is no hasMorePages() helper, derive it from count($items) === $perPage and stop.

Cache reference data, stream transactional data

Teams, competitions, venues, and seasons change rarely. Wrap them:

$teams = Cache::remember('dc:teams', now()->addHours(6), fn () => $repo->all());

Per-athlete session and performance data should not be cached — pull it into your own tables and let Eloquent be the cache.

Respect rate limits at the queue layer

Put a limiter in front of sync jobs rather than sleeping inside them:

public function middleware(): array
{
    return [new \Illuminate\Queue\Middleware\RateLimited('datacenter')];
}

with RateLimiter::for('datacenter', fn ($job) => Limit::perMinute(60)); — tune to the provider's documented ceiling. This keeps worker pools from burning the quota during a backfill.

Multi-tenant / multi-org credentials

If you serve more than one club or organization, avoid mutating the singleton's config at runtime. Instead resolve a fresh client per tenant:

$client = app(DataCenterFactory::class)->make($tenant->dc_api_key);

If the package doesn't ship a factory, write one that constructs the client with explicit config rather than reading config() inside the constructor.

Testing seam

Whatever the client looks like, fake at the HTTP layer and let the SDK run:

Http::fake(['*/teams*' => Http::response(['data' => [/* fixture */]])]);

This exercises the SDK's own hydration and catches breaking changes on upgrade — which matters far more than testing your own calls. If the SDK uses its own Guzzle client instead of Http, bind a stub of the client class in the container and assert on the calls your repository makes.

Gotchas and Tips

The singleton will serve stale config. The service provider almost certainly registers the client as a singleton. In Octane, long-running workers, and tests that change config() between cases, you'll get the old base URL or key. Fix: app()->forgetInstance(DataCenter::class) in tearDown(), and never change config mid-request in production.

Pin the version exactly. Zero dependents and a first release dated in the future means the public API is not stabilized. Use "velocity-sports-labs/datacenter-laravel-sdk": "0.x.y" with an exact pin or a tight ~ constraint, and commit composer.lock. Read the diff of any minor bump before merging.

Facade alias registration. Laravel's package auto-discovery registers the provider, not always the alias. If DataCenter:: throws "class not found," either add the alias to config/app.php or just use constructor injection. Don't fight it — injection is the better pattern anyway.

Inspect, then trust, the hydration layer. SDK DTOs for sports data are lossy by nature: unknown fields get dropped, nested stat maps get flattened, and null vs 0 matters enormously for averages. Before building stats on top of a DTO, json_encode both the raw API response and the DTO and diff them. Anything you need that's missing means calling the underlying HTTP client or extending the DTO.

Dates and timezones. Match/session timestamps in sports feeds are typically UTC ISO-8601, but "game date" is often a local calendar date. Cast with CarbonImmutable and store UTC in the DB; convert only at the presentation edge. Watch for DTOs that return strings rather than Carbon — casting in an accessor is cheaper than a migration later.

Error handling is the sharp edge. Confirm whether the SDK calls ->throw() internally or returns null on 4xx/5xx. Then:

try {
    $response = DataCenter::teams()->list();
} catch (\Illuminate\Http\Client\RequestException $e) {
    if ($e->response->status() === 429) {
        $this->release(60); // queue job, not a sleep()
    }
    throw $e;
}

A silent null return is worse than an exception — if that's the behavior, add an assertion wrapper in your repository so a failed sync doesn't quietly upsert an empty set.

Don't log the client. Debugging via ->withOptions(['debug' => true]) or dumping the client object will print the bearer token into laravel.log and into exception traces. If you need request tracing, add a Guzzle middleware that logs method + path + status only, and redact the Authorization header.

Extension points worth knowing. In rough order of usefulness: (1) publish and edit the config for timeout/retry; (2) Http::globalRequestMiddleware() if the SDK is built on the HTTP client — good for injecting a trace ID or a user agent the provider requires; (3) container rebinding of the client class for a subclass with extra methods; (4) a decorator around your repository for caching and metrics. Prefer 1 and 4; subclassing the SDK is the upgrade-hostile option.

Beware N+1 upstream calls. Hydrating a list of athletes and then calling ->team() or ->stats() per athlete can turn one page into hundreds of requests. Check the API docs for include=/expand= query parameters and pass them through the resource call's options array. If the SDK doesn't expose includes, that's a hard blocker for list-heavy screens and justification for a thin custom client instead.

Write one contract test against the live sandbox early. With no ecosystem around this package, your integration's real specification is a single test that hits a sandbox credential and asserts the shape of one list response and one detail response. Run it in CI nightly, not on every push, and let it fail loudly when the vendor changes a field name — that's your early warning system, and it's cheaper than discovering the break in a production sync.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums