├── composer.json ├── config └── unsplash.php ├── readme.md └── src ├── Facades └── Unsplash.php ├── Middleware └── UnsplashRateLimitMiddleware.php ├── UnsplashService.php └── UnsplashServiceProvider.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xchimx/laravel-unsplash", 3 | "description": "Laravel package for easy integration with the Unsplash API. It allows you to use the Unsplash API in your Laravel applications to fetch photos, collections, and user data.", 4 | "keywords": [ 5 | "laravel", 6 | "laravel-unsplash", 7 | "unsplash", 8 | "schottstaedt.net", 9 | "Tobias Schottstädt", 10 | "api", 11 | "photos", 12 | "images" 13 | ], 14 | "license": "MIT", 15 | "type": "library", 16 | "homepage": "https://www.schottstaedt.net", 17 | "authors": [ 18 | { 19 | "name": "Tobias Schottstädt", 20 | "email": "code@schottstaedt.net", 21 | "homepage": "https://www.schottstaedt.net", 22 | "role": "Developer" 23 | } 24 | ], 25 | "require": { 26 | "php": "^8.0", 27 | "laravel/framework": "^9.0|^10.0|^11.0|^12.0", 28 | "guzzlehttp/guzzle": "^7.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Xchimx\\UnsplashApi\\": "src/" 33 | } 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "Xchimx\\UnsplashApi\\UnsplashServiceProvider" 39 | ], 40 | "aliases": { 41 | "Unsplash": "Xchimx\\UnsplashApi\\Facades\\Unsplash" 42 | } 43 | } 44 | }, 45 | "support": { 46 | "issues": "https://github.com/xchimx/laravel-unsplash/issues", 47 | "source": "https://github.com/xchimx/laravel-unsplash" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /config/unsplash.php: -------------------------------------------------------------------------------- 1 | env('UNSPLASH_ACCESS_KEY', ''), 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Unsplash API Base URI 19 | |-------------------------------------------------------------------------- 20 | | 21 | | The base-URL for Unsplash API. 22 | | 23 | */ 24 | 25 | 'base_uri' => 'https://api.unsplash.com/', 26 | ]; 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # About Laravel-Unsplash 2 | 3 | A Laravel package for easy integration with the Unsplash API. It allows you to use the Unsplash API in your Laravel applications to fetch photos, collections, and user data. 4 | Current supported Laravel versions 9/10/11/12 5 | 6 | - [Laravel](https://laravel.com/) 7 | - [Urlscan](https://unsplash.com/) 8 | - [Schottstaedt](https://www.schottstaedt.net/) 9 | 10 | --- 11 | * [About Laravel-Unsplash](#about-laravel-unsplash) 12 | * [Installation](#installation) 13 | * [Usage](#usage) 14 | * [Basic Usage](#basic-usage) 15 | * [Using the Facade](#using-the-facade) 16 | * [UnsplashService Methods](#unsplashservice-methods) 17 | * [Controller Examples](#controller-examples) 18 | * [1. searchPhotos](#1-searchphotos) 19 | * [2. searchPhotosAdvanced](#2-searchphotosadvanced) 20 | * [3. getPhoto](#3-getphoto) 21 | * [4. getRandomPhoto](#4-getrandomphoto) 22 | * [5. getPhotoDownloadLink](#5-getphotodownloadlink) 23 | * [6. listCollections](#6-listcollections) 24 | * [7. getCollection](#7-getcollection) 25 | * [8. getUser](#8-getuser) 26 | * [9. getUserPhotos](#9-getuserphotos) 27 | * [10. searchCollections](#10-searchcollections) 28 | * [11. withOptions](#11-withoptions) 29 | * [Rate Limiting Middleware](#rate-limiting-middleware) 30 | * [Configuration](#configuration) 31 | * [Usage](#usage-1) 32 | * [Error Handling](#error-handling) 33 | * [Notes on the Unsplash API](#notes-on-the-unsplash-api) 34 | * [License](#license) 35 | 36 | 37 | ## Installation 38 | You can install the package via composer: 39 | 40 | ```bash 41 | composer require xchimx/laravel-unsplash 42 | ``` 43 | 44 | Publish the config file using the artisan CLI tool: 45 | 46 | ```php 47 | php artisan vendor:publish --provider="Xchimx\UnsplashApi\UnsplashServiceProvider" --tag="config" 48 | ``` 49 | 50 | finally set the [API Key](https://urlscan.io/docs/api/) in your ENV file: 51 | ```php 52 | UNSPLASH_ACCESS_KEY=your_unsplash_access_key 53 | ``` 54 | Optional Rate Limiting settings in ENV file: 55 | ```php 56 | UNSPLASH_RATE_LIMITING_ENABLED=true 57 | UNSPLASH_RATE_LIMITING_THRESHOLD=10 58 | ``` 59 | ## Usage 60 | ### Basic Usage 61 | You can use the UnsplashService in your controllers by injecting it via Dependency Injection: 62 | 63 | ```php 64 | use Xchimx\UnsplashApi\UnsplashService; 65 | 66 | class UnsplashController extends Controller 67 | { 68 | protected $unsplashService; 69 | 70 | public function __construct(UnsplashService $unsplashService) 71 | { 72 | $this->unsplashService = $unsplashService; 73 | } 74 | 75 | public function search() 76 | { 77 | $photos = $this->unsplashService->searchPhotos('Nature'); 78 | 79 | return view('unsplash.search', compact('photos')); 80 | } 81 | } 82 | ``` 83 | ### Using the Facade 84 | Alternatively, you can use the provided Unsplash facade: 85 | ```php 86 | use Xchimx\UnsplashApi\Facades\Unsplash; 87 | 88 | class UnsplashController extends Controller 89 | { 90 | public function search() 91 | { 92 | $photos = Unsplash::searchPhotos('Nature'); 93 | 94 | return view('unsplash.search', compact('photos')); 95 | } 96 | } 97 | ``` 98 | ## UnsplashService Methods 99 | The UnsplashService provides the following methods: 100 | 101 | 1. searchPhotos($query, $perPage = 10, $page = 1) 102 | 1. searchPhotosAdvanced(array $params) 103 | 1. getPhoto($id) 104 | 1. getRandomPhoto(array $params = []) 105 | 1. getPhotoDownloadLink($id) 106 | 1. listCollections($perPage = 10, $page = 1) 107 | 1. getCollection($id) 108 | 1. getUser($username) 109 | 1. getUserPhotos($username, $perPage = 10, $page = 1) 110 | 1. searchCollections($query, $perPage = 10, $page = 1) 111 | 1. withOptions(array $options) 112 | 113 | ## Controller Examples 114 | Here are comprehensive controller examples showing how to use the various methods of the UnsplashService in your Laravel controllers. 115 | ### 1. searchPhotos 116 | ```php 117 | namespace App\Http\Controllers; 118 | 119 | use Illuminate\Http\Request; 120 | use Xchimx\UnsplashApi\Facades\Unsplash; 121 | 122 | class UnsplashController extends Controller 123 | { 124 | public function search(Request $request) 125 | { 126 | $query = $request->input('query', 'Nature'); 127 | $photos = Unsplash::searchPhotos($query); 128 | 129 | return view('unsplash.search', compact('photos', 'query')); 130 | } 131 | } 132 | ``` 133 | Blade View: 134 | ```bladehtml 135 | @extends('layouts.app') 136 | 137 | @section('content') 138 |

Photo by {{ $photo['user']['name'] }}

139 | {{ $photo['alt_description'] }} 140 |

{{ $photo['description'] ?? 'No description available.' }}

141 | @endsection 142 | ``` 143 | ### 2. searchPhotosAdvanced 144 | ```php 145 | namespace App\Http\Controllers; 146 | 147 | use Illuminate\Http\Request; 148 | use Xchimx\UnsplashApi\Facades\Unsplash; 149 | 150 | class UnsplashController extends Controller 151 | { 152 | public function advancedSearch(Request $request) 153 | { 154 | $params = [ 155 | 'query' => $request->input('query', 'Nature'), 156 | 'color' => $request->input('color'), 157 | 'orientation' => $request->input('orientation'), 158 | 'per_page' => $request->input('per_page', 15), 159 | 'page' => $request->input('page', 1), 160 | ]; 161 | 162 | $params = array_filter($params); 163 | 164 | $response = Unsplash::searchPhotosAdvanced($params); 165 | 166 | $photos = $response['results']; 167 | 168 | return view('unsplash.advanced_search', compact('photos', 'params')); 169 | } 170 | } 171 | 172 | ``` 173 | ### 3. getPhoto 174 | ```php 175 | namespace App\Http\Controllers; 176 | 177 | use Xchimx\UnsplashApi\Facades\Unsplash; 178 | 179 | class UnsplashController extends Controller 180 | { 181 | public function show($id) 182 | { 183 | $photo = Unsplash::getPhoto($id); 184 | 185 | return view('unsplash.show', compact('photo')); 186 | } 187 | } 188 | ``` 189 | 190 | Blade View: 191 | ```bladehtml 192 | @extends('layouts.app') 193 | 194 | @section('content') 195 |

Photo by {{ $photo['user']['name'] }}

196 | {{ $photo['alt_description'] }} 197 |

{{ $photo['description'] ?? 'No description available.' }}

198 | @endsection 199 | 200 | ``` 201 | ### 4. getRandomPhoto 202 | ```php 203 | namespace App\Http\Controllers; 204 | 205 | use Xchimx\UnsplashApi\Facades\Unsplash; 206 | 207 | class RandomPhotoController extends Controller 208 | { 209 | public function show() 210 | { 211 | $photo = Unsplash::getRandomPhoto(); 212 | 213 | return view('photos.random', compact('photo')); 214 | } 215 | } 216 | 217 | ``` 218 | Blade View: 219 | ```bladehtml 220 | @extends('layouts.app') 221 | 222 | @section('content') 223 |

Random Photo

224 | {{ $photo['alt_description'] }} 225 |

Photo by {{ $photo['user']['name'] }}

226 | @endsection 227 | 228 | ``` 229 | 230 | ### 5. getPhotoDownloadLink 231 | ```php 232 | namespace App\Http\Controllers; 233 | 234 | use Xchimx\UnsplashApi\Facades\Unsplash; 235 | 236 | class UnsplashController extends Controller 237 | { 238 | public function download($id) 239 | { 240 | $downloadUrl = Unsplash::getPhotoDownloadLink($id); 241 | 242 | return redirect($downloadUrl); 243 | } 244 | } 245 | 246 | ``` 247 | 248 | ### 6. listCollections 249 | ```php 250 | namespace App\Http\Controllers; 251 | 252 | use Illuminate\Http\Request; 253 | use Xchimx\UnsplashApi\Facades\Unsplash; 254 | 255 | class CollectionController extends Controller 256 | { 257 | public function index(Request $request) 258 | { 259 | $page = $request->input('page', 1); 260 | $collections = Unsplash::listCollections(15, $page); 261 | 262 | return view('collections.index', compact('collections')); 263 | } 264 | } 265 | 266 | ``` 267 | 268 | Blade View: 269 | ```bladehtml 270 | @extends('layouts.app') 271 | 272 | @section('content') 273 |

Collections

274 | @foreach ($collections as $collection) 275 |
276 |

{{ $collection['title'] }}

277 |

{{ $collection['description'] ?? 'No description' }}

278 | View Details 279 |
280 | @endforeach 281 | @endsection 282 | 283 | 284 | ``` 285 | 286 | ### 7. getCollection 287 | ```php 288 | namespace App\Http\Controllers; 289 | 290 | use Xchimx\UnsplashApi\Facades\Unsplash; 291 | 292 | class CollectionController extends Controller 293 | { 294 | public function show($id) 295 | { 296 | $collection = Unsplash::getCollection($id); 297 | 298 | return view('collections.show', compact('collection')); 299 | } 300 | } 301 | 302 | ``` 303 | 304 | Blade View: 305 | ```bladehtml 306 | @extends('layouts.app') 307 | 308 | @section('content') 309 |

{{ $collection['title'] }}

310 |

{{ $collection['description'] ?? 'No description available.' }}

311 | 312 | @endsection 313 | 314 | ``` 315 | 316 | ### 8. getUser 317 | ```php 318 | namespace App\Http\Controllers; 319 | 320 | use Illuminate\Http\Request; 321 | use Xchimx\UnsplashApi\Facades\Unsplash; 322 | 323 | class UserController extends Controller 324 | { 325 | public function user($username, Request $request) 326 | { 327 | $user = Unsplash::getUser($name); 328 | 329 | return view('user', compact('user')); 330 | } 331 | } 332 | 333 | ``` 334 | 335 | ### 9. getUserPhotos 336 | ```php 337 | namespace App\Http\Controllers; 338 | 339 | use Illuminate\Http\Request; 340 | use Xchimx\UnsplashApi\Facades\Unsplash; 341 | 342 | class UserController extends Controller 343 | { 344 | public function photos($username, Request $request) 345 | { 346 | $page = $request->input('page', 1); 347 | $photos = Unsplash::getUserPhotos($username, 15, $page); 348 | 349 | return view('users.photos', compact('photos', 'username')); 350 | } 351 | } 352 | 353 | ``` 354 | 355 | Blade View: 356 | ```bladehtml 357 | @extends('layouts.app') 358 | 359 | @section('content') 360 |

Photos by {{ $username }}

361 | @foreach ($photos as $photo) 362 |
363 | {{ $photo['alt_description'] }} 364 |

{{ $photo['description'] ?? 'No description' }}

365 |
366 | @endforeach 367 | @endsection 368 | 369 | ``` 370 | 371 | ### 10. searchCollections 372 | ```php 373 | namespace App\Http\Controllers; 374 | 375 | use Illuminate\Http\Request; 376 | use Xchimx\UnsplashApi\Facades\Unsplash; 377 | 378 | class CollectionController extends Controller 379 | { 380 | public function search(Request $request) 381 | { 382 | $query = $request->input('query', 'Nature'); 383 | $page = $request->input('page', 1); 384 | 385 | $collections = Unsplash::searchCollections($query, 15, $page); 386 | 387 | return view('collections.search', compact('collections', 'query')); 388 | } 389 | } 390 | 391 | ``` 392 | Blade View: 393 | ```bladehtml 394 | @extends('layouts.app') 395 | 396 | @section('content') 397 |

Search Results for Collections: "{{ $query }}"

398 | @foreach ($collections['results'] as $collection) 399 |
400 |

{{ $collection['title'] }}

401 |

{{ $collection['description'] ?? 'No description' }}

402 | View Photos 403 | @endforeach 404 | @endsection 405 |
406 | ``` 407 | 408 | ### 11. withOptions 409 | ```php 410 | namespace App\Http\Controllers; 411 | 412 | use Illuminate\Http\Request; 413 | use Xchimx\UnsplashApi\Facades\Unsplash; 414 | 415 | class CollectionController extends Controller 416 | { 417 | public function searchWithTimeout(Request $request) 418 | { 419 | // Use withOptions to set custom Guzzle options, e.g., timeout 420 | $photos = Unsplash::withOptions(['timeout' => 2])->searchPhotos('Nature'); 421 | 422 | return view('unsplash.search', compact('photos')); 423 | } 424 | } 425 | 426 | ``` 427 | 428 | Blade View: 429 | ```bladehtml 430 | @extends('layouts.app') 431 | 432 | @section('content') 433 |

Search Results for Collections: "{{ $query }}"

434 | @foreach ($collections['results'] as $collection) 435 |
436 |

{{ $collection['title'] }}

437 |

{{ $collection['description'] ?? 'No description' }}

438 | View Photos 439 | @endforeach 440 | @endsection 441 |
442 | ``` 443 | 444 | ## Rate Limiting Middleware 445 | This package includes middleware to monitor and handle the Unsplash API rate limits. The middleware is enabled by default and can be customized in the configuration options. 446 | 447 | ### Configuration 448 | The rate limiting settings are located in config/unsplash.php: 449 | ```php 450 | 'rate_limiting' => [ 451 | 'enabled' => env('UNSPLASH_RATE_LIMITING_ENABLED', true), 452 | 'threshold' => env('UNSPLASH_RATE_LIMITING_THRESHOLD', 10), 453 | ], 454 | ``` 455 | - enabled: Enables or disables the rate limiting middleware. 456 | - threshold: The threshold for remaining requests at which the middleware intervenes. 457 | 458 | ### Usage 459 | To use the middleware in your routes, add it as follows: 460 | ```php 461 | Route::middleware(['unsplash.rate_limit'])->group(function () { 462 | Route::get('/unsplash/search', [UnsplashController::class, 'search'])->name('unsplash.search'); 463 | // Other routes... 464 | }); 465 | 466 | ``` 467 | 468 | ## Error Handling 469 | It's important to handle errors during API calls, especially when communicating with external services. 470 | ```php 471 | public function search(Request $request) 472 | { 473 | try { 474 | $photos = Unsplash::searchPhotos('Nature'); 475 | } catch (\Exception $e) { 476 | // Log the error or display a user-friendly message 477 | return back()->withErrors('There was a problem communicating with the Unsplash API.'); 478 | } 479 | 480 | return view('unsplash.search', compact('photos')); 481 | } 482 | 483 | ``` 484 | ## Notes on the Unsplash API 485 | - Rate Limits: The Unsplash API has rate limits. Be sure to monitor the number of requests and use the provided middleware. 486 | - Attribution: When using photos, you must credit the photographers according to Unsplash's guidelines. 487 | - API Documentation: For more details, refer to the Unsplash API Documentation. 488 | ## License 489 | This package is released under the MIT License. 490 | -------------------------------------------------------------------------------- /src/Facades/Unsplash.php: -------------------------------------------------------------------------------- 1 | json([ 21 | 'error' => 'Unsplash API rate limit reached. Please try again later.', 22 | ], 429); 23 | } 24 | 25 | $response = $next($request); 26 | 27 | $this->updateRateLimitInfo(); 28 | 29 | return $response; 30 | } 31 | 32 | protected function updateRateLimitInfo() 33 | { 34 | $headers = Unsplash::getLastResponseHeaders(); 35 | 36 | if ($headers) { 37 | $rateLimit = $headers['X-Ratelimit-Limit'][0] ?? null; 38 | $rateRemaining = $headers['X-Ratelimit-Remaining'][0] ?? null; 39 | 40 | if ($rateLimit !== null) { 41 | Cache::put('unsplash_rate_limit', $rateLimit, now()->addHour()); 42 | } 43 | 44 | if ($rateRemaining !== null) { 45 | Cache::put('unsplash_rate_remaining', $rateRemaining, now()->addHour()); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/UnsplashService.php: -------------------------------------------------------------------------------- 1 | client = new Client([ 19 | 'base_uri' => config('unsplash.base_uri'), 20 | 'headers' => [ 21 | 'Authorization' => 'Client-ID '.config('unsplash.unsplash_access_key'), 22 | 'Accept-Version' => 'v1', 23 | ], 24 | ]); 25 | } 26 | 27 | protected function request($method, $uri, $options = []) 28 | { 29 | $options = array_merge_recursive($options, $this->temporaryOptions); 30 | 31 | $response = $this->client->{$method}($uri, $options); 32 | 33 | $this->storeLastResponseHeaders($response); 34 | 35 | $this->temporaryOptions = []; 36 | 37 | return $response; 38 | } 39 | 40 | protected function storeLastResponseHeaders(ResponseInterface $response): void 41 | { 42 | $this->lastResponseHeaders = $response->getHeaders(); 43 | } 44 | 45 | public function getLastResponseHeaders(): array 46 | { 47 | return $this->lastResponseHeaders; 48 | } 49 | 50 | public function searchPhotos($query, $perPage = 10, $page = 1) 51 | { 52 | $response = $this->request('get', 'search/photos', [ 53 | 'query' => [ 54 | 'query' => $query, 55 | 'per_page' => $perPage, 56 | 'page' => $page, 57 | ], 58 | ]); 59 | 60 | return json_decode($response->getBody(), true); 61 | } 62 | 63 | public function searchPhotosAdvanced(array $params) 64 | { 65 | $response = $this->request('get', 'search/photos', [ 66 | 'query' => $params, 67 | ]); 68 | 69 | return json_decode($response->getBody(), true); 70 | } 71 | 72 | public function getPhoto($id) 73 | { 74 | $response = $this->request('get', "photos/{$id}"); 75 | 76 | return json_decode($response->getBody(), true); 77 | } 78 | 79 | public function getRandomPhoto(array $params = []) 80 | { 81 | $response = $this->request('get', 'photos/random', [ 82 | 'query' => $params, 83 | ]); 84 | 85 | return json_decode($response->getBody(), true); 86 | } 87 | 88 | public function getPhotoDownloadLink($id) 89 | { 90 | $response = $this->request('get', "photos/{$id}/download"); 91 | 92 | $data = json_decode($response->getBody(), true); 93 | 94 | return $data['url'] ?? null; 95 | } 96 | 97 | public function listCollections($perPage = 10, $page = 1) 98 | { 99 | $response = $this->request('get', 'collections', [ 100 | 'query' => [ 101 | 'per_page' => $perPage, 102 | 'page' => $page, 103 | ], 104 | ]); 105 | 106 | return json_decode($response->getBody(), true); 107 | } 108 | 109 | public function getCollection($id) 110 | { 111 | $response = $this->request('get', "collections/{$id}"); 112 | 113 | return json_decode($response->getBody(), true); 114 | } 115 | 116 | public function searchCollections($query, $perPage = 10, $page = 1) 117 | { 118 | $response = $this->request('get', 'search/collections', [ 119 | 'query' => [ 120 | 'query' => $query, 121 | 'per_page' => $perPage, 122 | 'page' => $page, 123 | ], 124 | ]); 125 | 126 | return json_decode($response->getBody(), true); 127 | } 128 | 129 | public function getUser($username) 130 | { 131 | $response = $this->request('get', "users/{$username}"); 132 | 133 | return json_decode($response->getBody(), true); 134 | } 135 | 136 | public function getUserPhotos($username, $perPage = 10, $page = 1) 137 | { 138 | $response = $this->request('get', "users/{$username}/photos", [ 139 | 'query' => [ 140 | 'per_page' => $perPage, 141 | 'page' => $page, 142 | ], 143 | ]); 144 | 145 | return json_decode($response->getBody(), true); 146 | } 147 | 148 | public function withOptions(array $options) 149 | { 150 | $this->temporaryOptions = $options; 151 | 152 | return $this; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/UnsplashServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../config/unsplash.php', 'unsplash'); 17 | 18 | $this->app->singleton('unsplash', function ($app) { 19 | return new UnsplashService; 20 | }); 21 | } 22 | 23 | /** 24 | * Bootstrap services. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | $this->publishes([ 31 | __DIR__.'/../config/unsplash.php' => config_path('unsplash.php'), 32 | ], 'config'); 33 | 34 | $this->app['router']->aliasMiddleware('unsplash.rate_limit', \Xchimx\UnsplashApi\Middleware\UnsplashRateLimitMiddleware::class); 35 | 36 | $this->publishes([ 37 | __DIR__.'/Middleware/UnsplashRateLimitMiddleware.php' => app_path('Http/Middleware/UnsplashRateLimitMiddleware.php'), 38 | ], 'middleware'); 39 | } 40 | } 41 | --------------------------------------------------------------------------------