requestlab/vimeo-bundle
Symfony bundle that integrates Vimeo’s API into PHP applications, offering services for video uploads, metadata retrieval, and embed configuration. Streamlines setup and dependency injection for Vimeo-powered features.
First, accept the framing: this is not a Laravel package. requestlab/vimeo-bundle is a Symfony 2 bundle. There is no service provider to register, no facade, no config/vimeo.php it will read. Laravel has no bundle loader, so composer require requestlab/vimeo-bundle inside a Laravel app either fails on platform/constraint resolution or, if forced with --ignore-platform-reqs, drags EOL Symfony components into your dependency graph for zero functional gain. Do not install it.
Where to look first. Read the repository, not the docs. In this order:
composer.json — the PHP and symfony/* constraints tell you immediately whether the code is portable at all. Expect ~2.x Symfony pins, which means PHP 5.5–7.x era.DependencyInjection/ — the extension class and Configuration.php define the config tree (API credentials, app id/secret, token). This is the part that does not port; it tells you what configuration keys the client needs.Services/ (or wherever the Vimeo client is constructed) — this is the only part worth reading. Look for how it builds the client, which endpoints it calls, and whether it can be instantiated without the container.First use case: a two-day spike behind your own interface. Before touching the bundle, define the contract you actually want:
// app/Contracts/VideoProvider.php
namespace App\Contracts;
interface VideoProvider
{
public function createUpload(string $name, array $privacy = []): UploadTicket;
public function uploadFile(UploadTicket $ticket, string $path): string; // returns provider video id
public function status(string $providerId): VideoStatus; // queued|transcoding|available|failed
public function embed(string $providerId): string; // iframe url / html
public function delete(string $providerId): void;
}
Then implement it once against vimeo/vimeo-api (official, actively maintained, PHP 8 compatible, framework-agnostic). That implementation is your correctness baseline. If you then choose to read the bundle's service code for hints on endpoint shapes or privacy arguments, you are comparing against a known-good reference rather than inheriting a 2014 wrapper.
Do not sequence it the other way. Reading the bundle first will teach you a stale API surface and Symfony conventions you then have to unlearn.
Wrap the vendor in a service provider, not in your controllers. The single pattern that matters: bind VideoProvider in a VideoServiceProvider, resolve it via constructor injection, and keep Vimeo types out of your domain models.
// app/Providers/VideoServiceProvider.php
public function register(): void
{
$this->app->singleton(UploadTicket::class, fn () => null); // no-op; see below
$this->app->bind(VideoProvider::class, fn ($app) => new VimeoProvider(
clientId: config('vimeo.client_id'),
clientSecret: config('vimeo.client_secret'),
accessToken: config('vimeo.access_token'),
http: $app->make(\Illuminate\Http\Client\Factory::class),
));
}
// config/vimeo.php
return [
'client_id' => env('VIMEO_CLIENT_ID'),
'client_secret' => env('VIMEO_CLIENT_SECRET'),
'access_token' => env('VIMEO_ACCESS_TOKEN'), // prefer a secrets manager in prod
'privacy' => [
'default_view' => env('VIMEO_DEFAULT_VIEW', 'disable'), // anyone|contacts|disable|unlisted|password
'embed' => env('VIMEO_EMBED', 'private'),
'allowed_domains' => array_filter(explode(',', (string) env('VIMEO_ALLOWED_DOMAINS', ''))),
],
'embed_preset_id' => env('VIMEO_EMBED_PRESET'),
];
Model the async state machine explicitly. This is the pattern the bundle encodes and the one most Laravel ports get wrong. An upload is not a synchronous create — it is: create ticket → PUT the bytes to the ticket's upload_link → poll the video URI until status == 'available'. Vimeo transcodes outside your request cycle.
// app/Jobs/UploadVideoToVimeo.php
public function handle(VideoProvider $vimeo): void
{
$ticket = $vimeo->createUpload($this->video->title, $this->privacyArgs());
$providerId = $vimeo->uploadFile($ticket, $this->video->local_path);
$this->video->update([
'provider' => 'vimeo',
'provider_id' => $providerId,
'status' => 'processing',
]);
PollVimeoStatus::dispatch($this->video)->delay(now()->addSeconds(30));
}
Keep your ID authoritative. Store videos.id as your own ULID and videos.provider_id as a nullable foreign key to the vendor. Never key app tables on the Vimeo id — that makes a vendor swap a data migration instead of a config change.
Poll, but back off and bound it. A PollVimeoStatus job that re-dispatches with increasing delay (30s → 60s → 120s → 300s, max ~30 attempts) and marks failed on timeout is the whole implementation. Cap total attempts so a stuck transcode doesn't poll forever and eat your rate limit.
Where the bundle's config tree maps to Laravel. Its Symfony extension class exists purely to read YAML and register services. The Laravel equivalent is config/vimeo.php + env vars + the singleton binding above. Do not try to reimplement the config tree, DI tags, or compiler passes — that's the part the TPM correctly flags as unportable and worthless on this side.
If you must port the code (not the package). Copy the client class into app/Services/Vimeo/, delete every use Symfony\... import, replace container lookups with constructor arguments, and add tests immediately. Vendor it — don't leave a composer.json requirement pointing at the upstream repo, or the Symfony transitive pins come back silently on the next composer update.
composer why-not before anything else. Run composer why-not requestlab/vimeo-bundle against your target app. A hard conflict with illuminate/* and PHP 8.1+ is the expected and correct result. If you find yourself reaching for --ignore-platform-reqs, stop — you're trading a clean lockfile for an EOL runtime surface.
Configuration is not portable, and that's fine. The bundle's value is almost entirely its wiring. Once you strip the wiring you're left with a thin HTTP wrapper you could rewrite in an afternoon. Price the port accordingly and don't be surprised when the "extraction" is 150 lines.
No retry, no backoff, no caching in the original. Expect bare synchronous calls. Add exponential backoff on 429 and 5xx (Laravel's Http::retry(3, 200, throw: false) plus a Retry-After-aware variant), and cache metadata/embed responses that don't need to be fresh. Bulk imports will trip rate limits that the package never handled.
Never upload inside a web request. Long-running PUTs to a ticket's upload link will hit PHP max_execution_time, gateway timeouts, and hold a PHP-FPM worker. Always queue. Same for status polling.
Failure modes to instrument for, because the package gives you no visibility into any of them: token expiry surfacing as an opaque 401; expired upload tickets mid-flow (they are short-lived and single-use — create one per attempt, don't cache them); rate-limit exhaustion during bulk operations; and silent breakage when Vimeo changes a response shape the wrapper doesn't validate. Log the raw status code and body at the boundary, then discard it.
Validate responses, don't trust the schema. Pin your own DTOs (UploadTicket, VideoStatus) and hydrate them defensively — a missing status key should raise an alert, not a null-propagated TypeError in a Blade template.
Auth model matters more than the wrapper. Verify whether the upstream code targets API v3 with access tokens or the older v2 auth flow. If it predates v3, its upload-ticket semantics are wrong for current Vimeo and the reference value of the code drops to near zero — only the privacy/embed-preset concepts survive.
Privacy settings are a product decision baked into the API call. Vimeo's model (anyone, contacts, disable, unlisted, password, plus embed allowlists per domain) maps directly onto your entitlement tiers. Decide this before writing the integration, and centralise it in the VideoProvider implementation so gating rules aren't scattered through controllers.
Zero stars and zero dependents is a signal, not a score. The reported "opportunity" number reflects market whitespace — demand for Vimeo integration with no maintained package filling it. It is not a quality or adoption signal. The correct reading is build, not adopt.
Extension points are your own. There are none upstream worth relying on — no events, no contracts to implement, no test suite indicating intended seams. Your extension point is the VideoProvider interface. Keep it small (createUpload, uploadFile, status, embed, delete) so swapping to S3 + a transcoder, or to another vendor entirely, is a new class and a config change.
Don't let the fork rot. If you vendor any of this code, name an owner in the repo, add feature tests against a mocked HTTP client (never live credentials in CI), and put "check Vimeo API changelog" on a recurring calendar item. The realistic long-term cost here isn't the initial port — it's the unattended maintenance of an integration nobody remembers owning.
How can I help you explore Laravel packages today?