Larasources
Larasources integrates external data sources into your Eloquent models, with caching, retry and rate limiting built in. You work with external APIs through model-like abstractions, without forcing those APIs into your own database tables.
Each source declares its fillable fields, casts, mappings and origin (the API client). The domain model stays clean, the integration layer stays separate, and the cached external state lives in one dedicated table.
Requirements: PHP 8.4+, Laravel 9+.
Installation
composer require edulazaro/larasources php artisan vendor:publish --provider="EduLazaro\Larasources\LarasourcesServiceProvider" php artisan migrate
Configuration
Origin credentials live in config/larasources.php under origins, keyed by the origin's alias:
'origins' => [ 'my_provider' => [ 'api_key' => env('MY_PROVIDER_API_KEY'), 'sandbox' => env('MY_PROVIDER_SANDBOX', false), ], ],
MY_PROVIDER_API_KEY=your_api_key MY_PROVIDER_SANDBOX=true
Retry and rate limiting are declared here too, and applied automatically to every origin call.
Attaching a source to a model
use Illuminate\Database\Eloquent\Model; use EduLazaro\Larasources\Concerns\HasSources; use App\Sources\WeatherSource; class City extends Model { use HasSources; protected array $sources = [ 'weather' => WeatherSource::class, ]; }
Reading and writing
$city = City::find(1); // Autoloaded from cache, fetched on miss $weather = $city->source('weather'); echo $weather->temperature; echo $weather->humidity; $city->source('weather')->save(); // push to the API and persist locally $fresh = $city->source('weather')->fetch(); // force refresh, bypassing cache $city->source('weather')->delete(); // delete remote and clear cache
Defining a Source
namespace App\Sources; use EduLazaro\Larasources\Source; use EduLazaro\Larasources\Attributes\UsesOrigin; use App\Origins\MyProviderOrigin; #[UsesOrigin(MyProviderOrigin::class)] class WeatherSource extends Source { protected $fillable = [ 'temperature', 'humidity', 'description', ]; protected $casts = [ 'temperature' => 'float', 'humidity' => 'integer', ]; protected function arguments(): array { return [ 'city_id' => 'external_id', // maps to $city->external_id ]; } public function getFeelsLikeAttribute(): float { return $this->temperature - ($this->humidity / 10); } }
Defining an Origin
The Origin is the API client, decoupled from the data shape:
namespace App\Origins; use EduLazaro\Larasources\Origins\Origin; use Illuminate\Support\Facades\Http; class MyProviderOrigin extends Origin { public static function getAlias(): string { return 'my_provider'; } public function fetch(array $arguments = []): array { $response = Http::withToken($this->getConfig('api_key')) ->get('https://api.example.com/weather/' . $arguments['city_id']); return $response->json(); } public function save(array $data): array { $response = Http::withToken($this->getConfig('api_key')) ->post('https://api.example.com/weather', $data); return $response->json(); } public function delete(): bool { return true; } }
Variants and runtime arguments
Variants handle multiple modes per source: sale vs rent for a listing, current vs forecast for weather:
$city->source('weather')->setVariant('forecast')->fetch(); $city->source('weather', ['city_id' => 'custom_id'])->fetch();
Caching
Sources are cached in the sources table through the SourceRecord model, keyed by (sourceable, name, variant).
if ($source->getRecord()) { // has been fetched or saved at least once } $source->clear();
Error handling
use EduLazaro\Larasources\Exceptions\OriginException; try { $weather = $city->source('weather')->fetch(); } catch (OriginException $e) { Log::error('Provider error: ' . $e->getMessage()); }
Testing
$mock = new WeatherSource(['temperature' => 22.5, 'humidity' => 60]); $city->mockSource(WeatherSource::class, $mock); $weather = $city->source('weather'); // the mocked instance
API reference
Source
| Method | Does |
|---|---|
fetch() |
Pull fresh data from the origin |
save() |
Push current attributes to the origin and persist |
saveToOrigin() |
Push without persisting locally |
delete() |
Delete remote and clear cache |
clear() |
Clear the cached record only |
origin() |
Get the resolved Origin instance |
getRecord() |
Get the underlying SourceRecord, or null |
setVariant(string $variant) |
Set the variant |
setVariantArguments(array $args) |
Pass runtime arguments |
Origin
fetch(array $arguments): array · save(array $data): array · delete(): bool · regenerate(): array · getAlias(): string
Bundled abstract Origins
| Class | For |
|---|---|
Origin |
Base class |
RemoteOrigin |
Generic REST client base |
AgentOrigin |
Agent-style integrations |
ScraperOrigin |
HTML scraping, with a getHtml() helper |
ScraperOrigin pairs naturally with Larascraper when the external source is a page rather than an API.
built and maintained by Edu Lazaro · MIT license