pdphilip / laravel-data-set
Eloquent style management of data sets in Laravel
Fund package maintenance!
PDPhilip
Installs: 9 502
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/pdphilip/laravel-data-set
Requires
- php: ^8.2
- illuminate/contracts: ^10.0||^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^9.0.0||^8.22.0
- pestphp/pest: ^3
- pestphp/pest-plugin-arch: ^3
- pestphp/pest-plugin-laravel: ^3
- phpstan/extension-installer: ^1
- phpstan/phpstan-deprecation-rules: ^2
- phpstan/phpstan-phpunit: ^2
README
Laravel Data Set
Eloquent-style querying for in-memory data sets. No database required.
Replace static arrays and config lookups with a queryable, model-like interface. Ideal for reference data (countries, currencies, timezones), test fixtures, and anywhere you need Eloquent ergonomics without a database.
// Define your data set class CountrySet extends DataSet { protected string $modelClass = CountryDataModel::class; protected function data(): array { return [ ['id' => 'US', 'name' => 'United States', 'currency' => 'USD', 'dial_code' => '+1'], ['id' => 'GB', 'name' => 'United Kingdom', 'currency' => 'GBP', 'dial_code' => '+44'], ['id' => 'DE', 'name' => 'Germany', 'currency' => 'EUR', 'dial_code' => '+49'], // ... ]; } } // Query it like Eloquent CountrySet::find('US')->name; // 'United States' CountrySet::where('currency', 'EUR')->get(); // Collection of European countries CountrySet::search('united')->pluck('name'); // ['United States', 'United Kingdom', ...] CountrySet::where('dial_code', '+1')->first()->currency; // 'USD'
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
Installation
composer require pdphilip/laravel-data-set
Quick Start
Inline usage
use PDPhilip\DataSet\DataSet; $set = new DataSet; $set->add(['name' => 'Alpha', 'status' => 'active', 'score' => 85]); $set->add(['name' => 'Beta', 'status' => 'inactive', 'score' => 42]); $set->add(['name' => 'Charlie', 'status' => 'active', 'score' => 91]); $set->where('status', 'active')->count(); // 2 $set->where('score', '>', 80)->orderBy('name')->get(); // Alpha, Charlie $set->search('beta')->first()->score; // 42
Extended data set (recommended)
Create a dedicated class with seeded data:
use PDPhilip\DataSet\DataSet; class TimezoneSet extends DataSet { protected function data(): array { return [ ['id' => 'UTC', 'name' => 'Coordinated Universal Time', 'offset' => '+00:00'], ['id' => 'EST', 'name' => 'Eastern Standard Time', 'offset' => '-05:00'], ['id' => 'PST', 'name' => 'Pacific Standard Time', 'offset' => '-08:00'], // ... ]; } } // Static facade - data loads once, cached per request TimezoneSet::find('EST')->name; // 'Eastern Standard Time' TimezoneSet::count(); // 3
Custom model class
Type your data with a custom model for IDE autocompletion:
use PDPhilip\DataSet\DataModel; /** * @property string $id * @property string $name * @property string $currency * @property string $dial_code */ class CountryDataModel extends DataModel {}
use PDPhilip\DataSet\DataSet; class CountrySet extends DataSet { protected string $modelClass = CountryDataModel::class; protected function data(): array { return [...]; } }
Now CountrySet::find('US') returns a CountryDataModel with typed properties.
Relationship-like access
Use a DataSet as a pseudo-relationship on your Eloquent models:
class User extends Model { public function country(): ?CountryDataModel { return CountrySet::find($this->country_code); } } $user->country()->name; // 'United States' $user->country()->dial_code; // '+1'
API Reference
CRUD
| Method | Returns | Description |
|---|---|---|
create(array $attributes) |
DataModel |
Create an unsaved model instance |
add(array $attributes) |
DataModel |
Create and save a model |
insert(array $rows) |
static |
Bulk insert rows |
$model->save() |
static |
Save or update a model |
$model->delete() |
void |
Remove a model from the set |
// Create without saving $model = $set->create(['name' => 'Draft']); $model->status = 'pending'; $model->save(); // Create and save in one step $model = $set->add(['name' => 'Ready', 'status' => 'active']); // Bulk insert $set->insert([ ['name' => 'Alpha', 'status' => 'active'], ['name' => 'Beta', 'status' => 'inactive'], ]); // Modify and re-save $model = $set->find('us'); $model->name = 'Updated'; $model->save(); // Delete $model->delete();
Records without an id get a UUID assigned automatically. Auto-generated IDs are hidden from toArray() output, so data round-trips cleanly (load from source, query, modify, export back). The ID is still accessible on the model for find(), save(), and delete() operations.
To use a custom primary key:
class MySet extends DataSet { protected string $primaryKey = 'code'; }
Query Methods
All query methods return a new instance, leaving the original untouched.
| Method | Description |
|---|---|
where(string $key, mixed $operator, mixed $value) |
Filter by field. Supports =, !=, <>, <, >, <=, >=, like, ===, !== |
where(string $key, mixed $value) |
Shorthand for where($key, '=', $value) |
whereNot(string $key, mixed $value) |
Shorthand for where($key, '!=', $value) |
whereStrict(string $key, mixed $value) |
Strict === comparison |
whereIn(string $key, array $values) |
Filter where field value is in array |
whereNotIn(string $key, array $values) |
Filter where field value is not in array |
whereBetween(string $key, array $range) |
Filter where field is between [$min, $max] |
whereNotBetween(string $key, array $range) |
Filter where field is outside [$min, $max] |
whereNull(string $key) |
Filter where field is null or missing |
whereNotNull(string $key) |
Filter where field is not null |
search(string $term) |
Case-insensitive search across all string fields |
orderBy(string $key, string $direction) |
Sort results (asc or desc) |
orderByDesc(string $key) |
Sort descending |
groupBy(string $key) |
Group results by field (applied on get()) |
limit(int $count) |
Limit result count |
offset(int $count) |
Skip first N results |
// Chaining $set->where('status', 'active') ->where('score', '>', 50) ->orderBy('name') ->limit(10) ->get(); // Dot notation for nested data $set->where('address.city', 'Sydney')->get(); // Array field membership $set->insert([ ['id' => '1', 'tags' => ['php', 'laravel']], ['id' => '2', 'tags' => ['js', 'react']], ]); $set->where('tags', 'php')->get(); // Row 1 // Group by $set->groupBy('browser')->get(); // => ['Chrome' => Collection, 'Firefox' => Collection, ...] // Clone isolation - queries never pollute the base set $active = $set->where('status', 'active'); $activeHigh = $active->where('score', '>', 80); $activeLow = $active->where('score', '<', 30); // $active, $activeHigh, $activeLow are all independent
Terminal Methods
| Method | Returns | Description |
|---|---|---|
get() |
Collection |
Execute query, return Collection of models |
all() |
Collection |
All records (ignores filters) |
first() |
DataModel|null |
First matching record |
find(mixed $id) |
DataModel|null |
Find by primary key |
fetch(string $key, mixed $value) |
DataModel|null |
Find first where key equals value |
firstOrCreate(array $attributes, array $values) |
DataModel |
Find matching or create with merged attributes |
count() |
int |
Count matching records |
exists() |
bool |
Any matches? |
pluck(string $value, ?string $key) |
Collection |
Pluck field values |
toArray() |
array |
Raw array output (auto-IDs stripped) |
paginate(int $perPage) |
LengthAwarePaginator |
Paginated results |
update(array $attributes) |
int |
Bulk update matching rows, returns count |
delete() |
int |
Bulk delete matching rows, returns count |
// Fetch - shorthand for where()->first() $set->fetch('email', 'john@example.com'); // First or create $set->firstOrCreate( ['email' => 'john@example.com'], // search by ['name' => 'John', 'status' => 'active'] // merge if creating ); // Bulk update $set->where('status', 'draft')->update(['status' => 'published']); // => 3 // Bulk delete $set->where('status', 'inactive')->delete(); // => 2
Static Facade
Extended DataSet classes support static method calls. The instance is cached per class for the duration of the request.
CountrySet::where('currency', 'EUR')->get(); CountrySet::find('US'); CountrySet::count(); CountrySet::search('island')->pluck('name');
Use flush() to clear the cached instance (useful in tests or Laravel Octane):
CountrySet::flush();
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.