samwilson/phpflickr
A PHP client for the Flickr API that handles OAuth authentication and provides simple methods for uploading photos, managing photosets, groups, galleries, and tags. Includes bundled API classes and helpers for building Flickr-powered apps.
Install and get credentials first, code second.
composer require samwilson/phpflickr
Then create an app at https://www.flickr.com/services/apps/create/ to get an API key and secret. A key alone is enough for public read calls; OAuth (and therefore the secret) is only needed to act on behalf of a user.
Minimal working call — no OAuth, just public data:
use phpFlickr\phpFlickr;
$flickr = new phpFlickr(config('services.flickr.key'), config('services.flickr.secret'));
$result = $flickr->photos_search([
'user_id' => '12345678@N00',
'per_page' => 12,
'extras' => 'url_m,url_o,date_upload,tags',
]);
foreach ($result['photos']['photo'] as $photo) {
echo (string) $photo['title'];
echo (string) $photo['url_m'];
}
Where to look first: the README.md (it has the auth walkthrough), the examples/ directory, and src/phpFlickr.php — the single class that does everything. There is no facade, no service provider, no config file. It is a thin wrapper: you construct it, you call Flickr method names on it, you get responses back.
First real use case: a read-only gallery. Pull a photostream or album into a page, cache it, build image URLs. That is 90% of what people use this package for and it requires zero auth plumbing. Add OAuth only when you need write/delete permissions (uploads, tagging, set management).
Method-name mapping. Flickr's REST methods are dispatched by magic naming: flickr.photos.search → $flickr->photos_search(), flickr.photosets.getPhotos → $flickr->photosets_getPhotos(). Dots become underscores. Always pass one associative array, never positional args:
$flickr->photos_getInfo(['photo_id' => $id, 'secret' => $secret]);
Responses are SimpleXML. Treat them as such: $result['photos']['photo'] gives the list, (string) $photo['id'] gets an attribute. Normalise to arrays once at the boundary so the rest of your app doesn't deal with XML:
function flickrArray(SimpleXMLElement $xml): array
{
return json_decode(json_encode($xml), true) ?? [];
}
Bind it in the container. Register a singleton for the anonymous (key-only) client, and build a per-user client on demand with that user's stored token:
$this->app->singleton('flickr', fn () => new phpFlickr(
config('services.flickr.key'),
config('services.flickr.secret')
));
// per-user
$client = new phpFlickr($key, $secret);
$client->setToken($user->flickr_token); // token + secret pair from your DB
OAuth 1.0a flow (three hops, store the request token between hops):
https://www.flickr.com/services/oauth/authorize?oauth_token=...&perms=read|write|delete.oauth_verifier for an access token; persist both token and token secret per user.The package ships an OAuth helper for exactly this — check the README's Authentication section for the method names in your installed version, since they've shifted between major releases. The token/secret pair is long-lived (until the user revokes it at flickr.com), so store it encrypted and treat it as a credential, not a session.
Pagination. Flickr caps per_page at 500 (default 100). Wrap it in a generator so callers don't hand-roll page loops:
foreach ($this->pages(fn ($page) => $flickr->photos_search([
'user_id' => $nsid, 'per_page' => 500, 'page' => $page,
])) as $photo) { /* ... */ }
Stop when $result['photos']['photo'] is empty or page >= pages. Note the page counter is a string in the XML.
URLs: prefer extras over guessing. Request url_s,url_m,url_l,url_o in extras rather than splicing farm/staticflickr URLs yourself — the URL format has changed over time and the extras are authoritative. Fall back to photos_getSizes when you need a size the extras don't cover.
Uploads belong in a queue job. sync_upload($file, $title, $description, $tags, $isPublic, ...) blocks on a multi-second HTTP request; async_upload returns a ticket you poll with photos_getTickets. Either way, run it from a job with retries, and remember uploads need write permission and the user to have granted it at auth time.
Cache aggressively. Flickr's per-key limits are real and unforgiving. A simple Cache::remember("flickr:{$method}:".md5(serialize($args)), 3600, ...) around every read call will save you an afternoon of debugging error code 105.
Testing. The package talks to Guzzle directly, so Laravel's Http::fake() will not intercept it. Either inject a Guzzle client with a MockHandler (if your version exposes that) or wrap the client behind your own small repository class and fake that in tests. The latter is worth doing regardless.
die_on_error defaults to true. On any API error the wrapper emits the error and terminates — catastrophic inside a web request or queue worker. Set $flickr->die_on_error = false; immediately after construction and check getErrorCode() / getErrorMsg() yourself.1 photo not found, 98 invalid auth, 99 insufficient permissions (usually a token issued with read when you need write), 105 service unavailable / rate limited, 112 method not found (usually a typo in your magic method name). 98/99 mean re-auth, not retry.read to write requires a full re-auth with the new perms; you cannot escalate. Decide delete vs write vs read before you ship.extras is a comma-separated string, not an array. Passing an array silently fails to include sizes — a very common "why is url_m empty?" bug. Also, url_* extras only appear if the owner permits sharing/downloads of that size.isset($photo['title']) returns false for empty strings; single-item results and lists have the same shape, so foreach is always safe but [0] is not; always cast with (string).12345678@N00) — they look numeric but aren't.dateupload, datetaken), while datetaken is also a string — pick one and normalise on ingest.samwilson/phpflickr is the maintained, namespaced, Guzzle-based line and is PHP 8 compatible. Older Dan Coulter copies on Packagist use unserialize() on API responses (object-injection risk) and break on modern PHP — check the lockfile if this dependency appears in an inherited project.request() path in the class, subclassing phpFlickr and overriding it is the cleanest place to add logging, retry-with-backoff on 105, and metrics without touching call sites.How can I help you explore Laravel packages today?