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

Flickr Laravel Package

rezzza/flickr

A PHP client for the Flickr API that wraps its REST endpoints in a clean, object-oriented interface. It handles OAuth authentication and lets you query photos, albums, and user data with ease, making Flickr integration straightforward in any PHP application.

Deep Wiki
Context7

Getting Started

Reality check first: this package was last released in July 2014, targets PHP 5.3+, and is archived. It predates PSR-7, PHP 7 scalar types, and Laravel 5.4's auto-discovery. Treat it as a legacy integration you are adopting deliberately (a Flickr asset library you must keep talking to), not as a dependency you pick for greenfield work. If you're starting fresh, budget an afternoon to read the source — there are ~5 classes and the README is thinner than the code.

Install

composer require rezzza/flickr

On modern PHP you will likely need --ignore-platform-reqs or a replace/fork, because the package constraint against the kriswallsmith/buzz HTTP client it depends on is ancient. The pragmatic path for a Laravel 10/11/12 app: fork it into app/Support/Flickr (or a private VCS repo), add "minimum-stability": "dev", and let Composer use your fork. Keeping it in vendor/ unmodified while patching is a losing battle.

Get credentials first. Go to the Flickr App Garden, create an app, and note the key and secret. Flickr uses OAuth 1.0a, so any endpoint that touches a user's own photos (upload, private photos, photosets) needs a per-user token + token secret, not just the app key. Public read methods (flickr.photos.search on public photos, flickr.photos.getInfo) work with the key alone.

Look here, in this order:

  1. Rezzza\Flickr\Metadata — holds the API key/secret and the OAuth token pair. This is your configuration object.
  2. Rezzza\Flickr\ApiFactory — builds the client from the metadata.
  3. The client's call($method, array $params) — the single method that matters. Everything in Flickr's API is flickr.* RPC over one endpoint.
  4. Rezzza\Flickr\Http\AdapterInterface — the seam you'll use for testing.

First use case — prove credentials and connectivity end-to-end before writing any domain code:

$metadata = new Metadata(config('services.flickr.key'), config('services.flickr.secret'));
$api = (new ApiFactory($metadata))->create();

// Echo is Flickr's cheapest "am I authenticated / is my signature valid" call.
$result = $api->call('flickr.test.echo', ['foo' => 'bar']);

Then a real one:

$result = $api->call('flickr.photos.search', [
    'text'     => 'edinburgh',
    'per_page' => 20,
    'page'     => 1,
    'extras'   => 'url_s,url_m,url_l,description,date_taken,owner_name',
    'format'   => 'json',
]);

If flickr.test.echo succeeds but authenticated calls return 98 / 99 error codes, your OAuth signature or token is wrong — not your method name.

Laravel wiring: bind it once as a singleton in a service provider, and read config from config/services.php:

// config/services.php
'flickr' => [
    'key'    => env('FLICKR_KEY'),
    'secret' => env('FLICKR_SECRET'),
],

// app/Providers/FlickrServiceProvider.php
$this->app->singleton(FlickrGateway::class, fn () => new FlickrGateway(
    config('services.flickr.key'),
    config('services.flickr.secret'),
));

Register per-user tokens at call time (see below) rather than putting them in the container.

Implementation Patterns

Wrap it. Always wrap it. The package's call() returns a decoded array/response object with Flickr's raw, inconsistent shape (photos.photo, photo, photoset, stat, code, message). Every consumer of your code should see your DTOs, not that. This also isolates you from the near-certainty that you'll swap the package out eventually.

final class FlickrGateway
{
    public function __construct(
        private Client $client,
        private ?string $token = null,
        private ?string $secret = null,
    ) {}

    public function withUserToken(string $token, string $secret): self
    {
        // Metadata is mutable; clone or rebuild so you don't leak one user's
        // token into another user's request inside a queue worker.
        $clone = clone $this;
        $clone->client = $this->rebuildWithToken($token, $secret);
        return $clone;
    }

    /** @return array{page:int,pages:int,total:int,photos:PhotoDto[]} */
    public function searchPhotos(string $text, int $page = 1, int $perPage = 50): array
    {
        $raw = $this->client->call('flickr.photos.search', [
            'text'     => $text,
            'page'     => $page,
            'per_page' => $perPage,
            'extras'   => 'url_s,url_m,url_l,url_o,owner_name,date_taken,tags',
            'format'   => 'json',
        ]);

        return $this->normalizeSearch($raw);
    }
}

Per-user OAuth token storage. The package has no token persistence. Store flickr_oauth_token and flickr_oauth_token_secret on your users table (encrypted casts), obtain them through your own OAuth 1.0a handshake routes (flickr.auth.oauth.getRequestToken → redirect to https://www.flickr.com/services/oauth/authorize?oauth_token=... → flickr.auth.oauth.getAccessToken on callback). Because the package's OAuth support is thin and old, many teams end up doing the handshake with guzzlehttp/oauth-subscriber and only use this package for the raw call(). That's fine — the package is essentially a signed HTTP client.

Use it from jobs, not controllers. Flickr is slow (300–1500 ms per call) and rate-limited. Patterns that work well:

  • FetchFlickrAlbum job per photoset, SyncFlickrPhoto job per photo; dispatch a batch after a search.
  • RateLimited / WithoutOverlapping middleware on the job, or a simple Redis::throttle('flickr')->allow(1)->every(1) guard — Flickr's documented limits are per-hour, per-key.
  • Persist the raw JSON payload to storage/app/flickr/{photo_id}.json next to the normalized row. When the API changes under you (it does), you can re-normalize without re-fetching.

Cache aggressively and paginate explicitly. Search responses are cheap to cache and expensive to re-fetch:

$key = 'flickr:search:'.md5($text.$page.$perPage);
$result = Cache::remember($key, now()->addHours(6), fn () => $gateway->searchPhotos($text, $page, $perPage));

Flickr's pagination lives at photos.page, photos.pages, photos.total. Loop for ($p = 1; $p <= $pages; $p++) and stop when photos.photo is empty — Flickr will happily return page=1 data if you overrun.

Build URLs yourself, don't trust the package to. Flickr's image URLs are constructed from the photo's farm, server, id, secret fields (plus url_s/url_m/url_l only if you asked for those extras). The suffix encodes size: _s square 75, _m 240, _b 1024, _c 800, _z 640. A single helper FlickrImage::url(array $photo, string $size) in your domain layer saves a hundred bugs:

https://farm{farm}.staticflickr.com/{server}/{id}_{secret}_{size}.jpg

Testing: never hit Flickr in tests. Either bind a fake FlickrGateway in the container (easiest, since you wrapped it), or — if you need to test the wrapper itself — implement Rezzza\Flickr\Http\AdapterInterface with a stub that returns canned XML/JSON, and inject it into ApiFactory. Note the package talks in the response format you ask for: it will parse XML by default in older versions, so if you're mocking, mock the format you actually request.

Artisan commands are underrated here. Flickr OAuth tokens expire-ish, albums drift, and photos get deleted upstream. A nightly flickr:reconcile command that walks your stored photo IDs and calls flickr.photos.getInfo (batched, throttled) keeps your local library honest and surfaces broken references before users do.

Gotchas and Tips

It's archived — assume zero upstream fixes. No PHP 8.1+ support. Expect deprecation notices from implicit nullable parameters, dynamic properties, and each()-era code if you dig into the OAuth signing. Run PHPUnit with convertDeprecationsToExceptions="false" while you're porting, or you'll drown.

OAuth 1.0a signing is the #1 failure point. Flickr is unforgiving: parameters must be sorted by key, percent-encoded per RFC 3986 before sorting, the base string must include the HTTP method and the base URL with no query string, and the signature must be built from the decoded secret + & + token secret. If you get oauth_problem=signature_invalid, the bug is almost always double-encoding (a + in a tag, a comma in extras, a non-ASCII character in a search term) or a param added after signing. Log the exact base string when debugging; you cannot reason about it otherwise.

Error handling is not exceptions. Flickr returns HTTP 200 with {"stat":"fail","code":98,"message":"Invalid auth token"}. The package may or may not throw — check the response body yourself and convert to a domain exception in your gateway. Useful codes: 98/99 bad/expired token (prompt re-auth), 105 service unavailable, 106 write restricted, 112 method not found (typo'd method name), 4 file not found, 5 file too large. Don't retry 98; do retry 105.

The response shape is inconsistent and untyped. flickr.photos.search returns photos.photo[]; flickr.photosets.getList returns photosets.photoset[]; flickr.photos.getInfo returns a bare photo. Some methods return a single item as an object instead of a one-element array. Normalize with a defensive helper (Arr::wrap on nested keys) rather than assuming arrays.

Uploads are a different endpoint. flickr.photos.upload-style calls won't work — uploads go to https://up.flickr.com/services/upload/ as multipart/form-data, and the package's buzz adapter is oriented at the REST endpoint. In practice you'll hand-roll uploads with Guzzle + oauth-subscriber, or add a custom adapter. Be aware that upload responses come back as XML with <photoid> regardless of your format parameter, and uploads are async: you must poll flickr.photos.upload.checkTickets before assuming the photo exists.

Concurrency / singleton leakage. Because Metadata holds mutable token state and you bound the client as a singleton, a long-running queue worker (queue:work, Octane) will serve user A's token to user B's job. Always clone/rebuild per authenticated call, or resolve the client per job. This is the single most dangerous production pitfall with this package.

Config quirks: the package wants the bare API key, not the "App Garden" app ID; Flickr's docs call the same string both things. Also, format=json requires nothing special, but if you request format=json without caring, some older package versions call json_decode on an XML body and silently return null — check for null explicitly and throw.

Log hygiene: never log the full signed URL or the token secret. Flickr's signed URLs contain the signature, and a leaked token secret lets anyone act as your user. Log the method name, param keys, response stat/code, and duration only.

Extension points if you need to modernize without rewriting call sites: implement AdapterInterface over Guzzle (gives you PSR-18, retries, and proper timeouts), keep call() as the only public surface, and replace the OAuth signer with guzzlehttp/oauth-subscriber. That's usually a 100-line adapter and turns a dead dependency into something you can reason about — at which point the honest question is whether to drop the package entirely and keep only your gateway.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route