PHPackages                             edulazaro/larasources - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. edulazaro/larasources

ActiveLibrary[Database &amp; ORM](/categories/database)

edulazaro/larasources
=====================

Integrate external data sources into Laravel models with caching, retry and rate-limiting. Sources are model-like classes; Origins are pluggable API clients.

0.6.0(1mo ago)02MITPHPPHP &gt;=8.4

Since Apr 28Pushed 1mo agoCompare

[ Source](https://github.com/edulazaro/larasources)[ Packagist](https://packagist.org/packages/edulazaro/larasources)[ RSS](/packages/edulazaro-larasources/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (5)Versions (3)Used By (0)

Larasources
===========

[](#larasources)

A Laravel package for integrating external data sources into your models with caching, retry, and rate-limiting built in. Work with external APIs using model-like abstractions, without forcing those APIs to live in your own database tables.

Why
---

[](#why)

Larasources lets your Eloquent models pull and push data from external services through a typed, declarative `Source` API. Each source declares its fillable fields, casts, mappings, and origin (the API client). Your domain model stays clean, the integration layer stays separated, and the cached external state lives in a single dedicated table.

Features
--------

[](#features)

- **Model-like Sources**: define external resources as classes with `fillable`, `casts`, accessors, and arguments
- **Origins**: pluggable API clients (`fetch`, `save`, `delete`) decoupled from the data shape
- **Built-in caching** through the `sources` table (`SourceRecord`)
- **Variants and arguments** to handle multiple operations or per-call parameters
- **Retry and rate-limiting** declared in config, applied automatically
- **Mockable** for tests via `mockSource()`

Requirements
------------

[](#requirements)

- PHP `>=8.4` (any future version included)
- Laravel `>=9.0` (any future version included)

Installation
------------

[](#installation)

```
composer require edulazaro/larasources
```

Publish the configuration and migrations:

```
php artisan vendor:publish --provider="EduLazaro\Larasources\LarasourcesServiceProvider"
php artisan migrate
```

Configuration
-------------

[](#configuration)

Origin-specific credentials live in `config/larasources.php` under `origins`, keyed by your origin's alias:

```
'origins' => [
    'my_provider' => [
        'api_key' => env('MY_PROVIDER_API_KEY'),
        'sandbox' => env('MY_PROVIDER_SANDBOX', false),
    ],
],
```

Then in `.env`:

```
MY_PROVIDER_API_KEY=your_api_key
MY_PROVIDER_SANDBOX=true
```

Usage
-----

[](#usage)

### 1. Add the `HasSources` trait to your model

[](#1-add-the-hassources-trait-to-your-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,
    ];
}
```

### 2. Read and write through the source

[](#2-read-and-write-through-the-source)

```
$city = City::find(1);

// Access external data (autoloaded from cache or fetched on miss)
$weather = $city->source('weather');
echo $weather->temperature;
echo $weather->humidity;

// Push data to the external API and persist locally
$city->source('weather')->save();

// Force refresh from the API (bypasses cache)
$fresh = $city->source('weather')->fetch();

// Delete remote and clear cache
$city->source('weather')->delete();
```

### 3. Define a Source

[](#3-define-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);
    }
}
```

### 4. Define an Origin (the API client)

[](#4-define-an-origin-the-api-client)

```
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;
    }
}
```

### 5. Variants and arguments

[](#5-variants-and-arguments)

Use variants to handle multiple modes per source (for example, `sale` vs `rent` for a property listing, or `current` vs `forecast` for weather):

```
$city->source('weather')->setVariant('forecast')->fetch();

// Pass runtime arguments
$city->source('weather', ['city_id' => 'custom_id'])->fetch();
```

Caching
-------

[](#caching)

Sources are cached automatically in the `sources` table (the `SourceRecord` model). Each record is keyed by `(sourceable, name, variant)`.

```
// Has it ever been fetched/saved?
if ($source->getRecord()) {
    // Data is cached locally
}

// Clear the cache for this source
$source->clear();
```

Error handling
--------------

[](#error-handling)

```
use EduLazaro\Larasources\Exceptions\OriginException;

try {
    $weather = $city->source('weather')->fetch();
} catch (OriginException $e) {
    Log::error('Provider error: ' . $e->getMessage());
}
```

Testing
-------

[](#testing)

Mock a source so it returns a fixed instance instead of hitting the origin:

```
$mock = new WeatherSource(['temperature' => 22.5, 'humidity' => 60]);
$city->mockSource(WeatherSource::class, $mock);

$weather = $city->source('weather');
// $weather is the mocked instance
```

API reference
-------------

[](#api-reference)

### Source

[](#source)

- `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 cached record only
- `origin()`: get the resolved Origin instance
- `getRecord()`: get the underlying `SourceRecord` (or `null`)
- `setVariant(string $variant)`: set the source's variant
- `setVariantArguments(array $args)`: pass runtime arguments

### Origin

[](#origin)

- `fetch(array $arguments): array`
- `save(array $data): array`
- `delete(): bool`
- `regenerate(): array`
- `getAlias(): string`

### Bundled abstract Origins

[](#bundled-abstract-origins)

- `Origin`: base class
- `RemoteOrigin`: generic REST client base
- `AgentOrigin`: for agent-style integrations
- `ScraperOrigin`: for HTML scraping with `getHtml()` helper

Credits
-------

[](#credits)

Developed by [Edu Lázaro](https://edulazaro.com).

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance91

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

43d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6a3c47449dfb2ec121aa410da024f47586b87cc2799a825f0418e6c5e5904955?d=identicon)[edulazaro](/maintainers/edulazaro)

---

Top Contributors

[![edulazaro](https://avatars.githubusercontent.com/u/7797530?v=4)](https://github.com/edulazaro "edulazaro (1 commits)")

---

Tags

laraveleloquentcachingintegrationrate limitingexternal-apidata-sources

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/edulazaro-larasources/health.svg)

```
[![Health](https://phpackages.com/badges/edulazaro-larasources/health.svg)](https://phpackages.com/packages/edulazaro-larasources)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

592452.8k2](/packages/spiritix-lada-cache)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[ntanduy/cloudflare-d1-database

Cloudflare D1 database driver for Laravel — full Eloquent &amp; Query Builder support.

246.8k](/packages/ntanduy-cloudflare-d1-database)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
