lensmedia/api.lensmedia.nl-bundle
A Symfony bundle that provides a ready-made client for the LensMedia API (api.lensmedia.nl), wiring services, configuration, and HTTP integration into your application so you can call LensMedia endpoints without building a client from scratch.
lensmedia/api.lensmedia.nl-bundle is a Symfony bundle, not a Laravel package. That single fact drives everything below: there is no LaravelServiceProvider, no package auto-discovery, and php artisan vendor:publish will not find it. Treat it as a source of an API client you either (a) consume in a Symfony service, or (b) port into Laravel with the Http facade.
Read the release history before anything else. The latest release is explicitly a marker/archival snapshot ("Bundle right before we switched to remove API platform… just a marker so we can look back"). It adds no features and fixes nothing. Treat this version as the last known-good reference implementation, not an upgrade target. In practice: the 2023 release you may already be on and this release are functionally equivalent for your purposes — the pinning and vendoring advice below matters more than the version bump.
Read these first (in this order):
composer.json — confirms the PHP/Symfony constraints, the HTTP client it pulls in (symfony/http-client / PSR-18), and whether a type: symfony-bundle autoload maps src/ to a namespace.src/DependencyInjection/Configuration.php — the config tree. This is where the real API contract lives: base URI, API key/token, timeouts, maybe a sandbox flag.src/DependencyInjection/*Extension.php — how services are wired and which aliases/IDs are public.src/Service/ (or src/Client/) — the class that actually issues requests. Everything else is plumbing around it.Minimal Laravel use case — authenticate and pull one page of results:
// config/services.php
'lensmedia' => [
'base_url' => env('LENSMEDIA_BASE_URL', 'https://api.lensmedia.nl'),
'token' => env('LENSMEDIA_TOKEN'),
'timeout' => 15,
],
// app/Services/LensMedia/Client.php
final class Client
{
public function __construct(private array $config) {}
public function get(string $path, array $query = []): array
{
return Http::baseUrl($this->config['base_url'])
->withToken($this->config['token'])
->timeout($this->config['timeout'])
->acceptJson()
->retry(2, 200)
->get($path, $query)
->throw()
->json();
}
}
The first endpoint worth hitting is whatever the bundle's primary resource is (media/asset listing or search). Confirm the exact path from the client class rather than guessing — the config's base_url and the client's path strings must be reconciled.
Port, don't install (Laravel). The bundle's value is its request/response mapping, not its DI wiring. Copy the client class, strip Symfony HttpClientInterface in favor of Illuminate\Support\Facades\Http, and keep the DTOs. You get the same endpoints without dragging Symfony's container into your app.
If you must run it as-is (Symfony side-car). Stand up a small Symfony app or use Symfony's HttpClient standalone via composer require symfony/http-client, then instantiate the client manually. Laravel's container can resolve a plain class via a bound closure:
$this->app->singleton(LensMediaClient::class, fn () => new LensMediaClient(
HttpClient::create(['base_uri' => config('services.lensmedia.base_url')]),
config('services.lensmedia.token'),
));
Wrap it in a repository, never inject the client into controllers. This is the pattern that survives the package being abandoned:
final class MediaRepository
{
public function __construct(private Client $client) {}
public function search(string $term, int $page = 1): Collection
{
return collect($this->client->get('/media', ['q' => $term, 'page' => $page])['data'])
->map(fn (array $row) => MediaData::fromArray($row));
}
}
Pagination. API clients of this vintage almost always return a meta/links block. Loop with a bounded page count and a while ($page <= $maxPages) guard — never trust an unbounded next link.
Bulk work goes on the queue. Sync calls from a web request will stall on a slow upstream. Dispatch a job per page, use RateLimited middleware, and cache by request hash with Cache::remember (short TTL — 60–300s) so repeated UI hits don't hammer the API.
Testing. Http::fake() covers everything, since you've replaced the transport:
Http::fake(['api.lensmedia.nl/*' => Http::response(['data' => []], 200)]);
Assert on Http::assertSent(fn ($req) => $req->hasHeader('Authorization')) to lock in auth behavior. If you keep the Symfony client, inject a MockHttpClient instead.
Contract tests against the real API. Because stars/dependents are zero and there is no forward roadmap (the maintainers have moved off this stack — see the marker release), add one opt-in integration test (nightly CI, real token) that hits a cheap endpoint. That's your only early warning that the upstream schema moved.
composer require will succeed, then nothing happens. There's no extra.laravel block; you'll get an "unrecognized package" non-error and a class that isn't bound. Expect to wire it manually or not at all."lensmedia/api.lensmedia.nl-bundle": "x.y.z" — check the real tag), commit composer.lock, and vendor the client code into app/ if the API is business-critical. Assume no upstream fixes ever.Configuration.php and the client class, since a "marker" release can still carry quiet changes made before the archive was cut. Diff the tag against your current version before switching.base_uri, api_key); environment variables are usually mapped by the bundle but not always. Read Configuration.php and set env vars to match exactly — a silently ignored key looks identical to an auth failure.base_uri. Symfony's HttpClient and Guzzle differ on how base_uri + relative path concatenate. Pick one convention (baseUrl without slash, paths starting with /) and be consistent, or you'll get //media 404s.ClientExceptionInterface/ServerExceptionInterface by default on 4xx/5xx; Guzzle (which Laravel's Http uses) throws RequestException only via ->throw(). Any try/catch copied from the bundle needs the exception class swapped.Data object (spatie/laravel-data or a plain fromArray) at the boundary so a missing field fails loudly in one place.timeout and use retry() with backoff on 429/5xx only. Retrying a 401 just burns your rate limit.lensmedia/api-client VCS repo under your org — now the clear winner given the explicit archive signal, (3) subclass the client if it isn't final. Prefer (2): it makes ownership and the abandonment explicit and reviewable.debug level; redact the Authorization header. Most integration failures on packages like this are a wrong base URL or an unset env var, not a code bug.How can I help you explore Laravel packages today?