Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Data Laravel Package

gcego/data

gcego/data is a lightweight Laravel/PHP package for working with structured data. It offers a fluent API for transforming, validating, and organizing data, helping developers keep application logic clean and maintainable, with sensible defaults and straightforward integration into existing projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Requirements and install. PHP 8.3+, Laravel 13 (Illuminate), and gcego/core ^1.0. Nothing else from the GCEGO suite is required — no gcego/components, no gcego/runtime, no blueprints/registry. That matters day-to-day: this is a standalone data engine you can drop into a normal Laravel app without adopting a wider framework.

composer require gcego/data:^1.0

The config file is only offered for publishing in the console, so run the publish command from a console context (not from a web-triggered installer) and review the new cache, transactions, performance and events sections. If you are multi-tenant, bind your own Contracts\TenantResolver over the default NullTenantResolver in a service provider before writing any source that declares TenantScoped.

Where to look first. Read in this order: Gcego\Data\Definitions\* (the typed declaration objects — this is the whole authoring surface), then Source::define(), then Contracts\* (SourceProvider, Operator, FilterValueType, ResolvesRecords, SupportsMutations, SupportsAggregation, ProvidesScopedQueries). If you are coming from a pre-1.0 build, docs/migration.md is not optional — the entire declaration API was replaced and new Source(...), Source::model()/table()/builder()/collection(), Property, Paginate, With, Includable and ->named() are gone.

First use case. A paginated, filterable, searchable listing endpoint backed by an Eloquent model. Define one source class, then hand the request input to Data::query() from the controller and shape the output with a transform. Skeleton (confirm exact argument ordering against the docs — every definition is a ::make() object passed once to Source::define()):

use Gcego\Data\Source;
use Gcego\Data\Definitions\{Name, Model, Columns, Filterables, Sortables, Search, Pagination, Transform};
use Gcego\Data\Filters\Filterable;
use Gcego\Data\Filters\Types\StringType;
use Gcego\Data\Filters\Operators\{Contains, GreaterThanOrEqual};
use Gcego\Data\Filters\Types\Integer;

final class PostSource extends Source
{
    public static function define(): Source
    {
        return Source::define(
            Name::make('posts'),
            Model::make(Post::class),
            Columns::make('id', 'title', 'published_at'),
            Filterables::make(
                Filterable::make('title', [StringType::make(), Contains::make()]),
                Filterable::make('views', [Integer::make(), GreaterThanOrEqual::make()]),
            ),
            Search::make('title'),
            Sortables::make('title', 'published_at'),
            Pagination::make(20),
            Transform::except('internal_notes'),
        );
    }
}
// controller
$result = Data::query(PostSource::class, $request->all(), $context);
return response()->json([
    'data' => $result->toArray(),
    'meta' => $result->meta(),
]);

Note Source::define() accepts a provider instance or class name on every Data method, and class names are validated before instantiation — so passing a class string from a controller is a supported, checked path.

Implementation Patterns

Class-based sources, container-resolved. Implement Contracts\SourceProvider on a plain class and let the container build it; inject collaborators (repositories, config, tenant context) through the constructor instead of hard-coding them in a static definition. This is the single biggest ergonomics decision in the package: a class-based source is testable, swappable, and works identically whether you pass the instance or the class name.

Declarative read path. Keep sources declarative — Columns, Constraints, Filterables, Sortables, Search, Pagination, Relations, Includables, Aggregates, Transform. Business logic goes into constraints (mandatory where), field security, before-save callbacks and transforms, not into the controller. The controller's job is: build QueryContext, call Data::query()/aggregate()/scopedQuery(), return DataResult.

Filter contracts first, then UI. Declare each filterable with an explicit value type and one or more operators. Compatibility is validated when the source is defined, and Equal is applied implicitly only when you list no operator at all. Use the sugar helpers — Filterable::text(), Filterable::enum(), Filterable::relation() — and the relation-existence operators (has, doesnt_have) for "has any comments" style facets. Custom domain comparisons belong in Contracts\CustomOperator; complex multi-column filters belong in Filters\CustomFilter + Contracts\FilterHandler. Use Filter::operatorName() (not the enum) when you need the request-facing operator name.

Relationships and aggregates. Declare relations and includables once; clients opt in with include=alias, including relation aggregates (RelationAggregate for count/sum/avg/min/max/exists as subqueries) and sort-by-alias. This is the feature that most reduces hand-written query code: dashboards can be a single query() with three included aggregate aliases instead of N queries. For true reporting, move to Data::aggregate() with an Aggregation and optional TimeBucket (day/week/month/year) — it runs in the database for Eloquent and query-builder sources, with the same authorization, constraints and filter allowlist as query().

Writes are opt-in and transaction-aware. Do not assume Data::create() works on a read-only definition: mutations require a Writable declaration (field allowlist, Laravel validation rules, permitted operations). Wrap multi-step writes in Data::transaction(); the transaction manager handles rollback, isolation levels, savepoints, and retries deadlocks/serialization failures with backoff and jitter. Put invariants in BeforeSave, authorization in policies (create, update, delete, restore, forceDelete), and side effects in listeners for RecordCreated/RecordUpdated/RecordDeleted/RecordRestored — all dispatched after commit, so queueing a search-index update there is safe. MutationResult gives you the affected ids back.

Tenancy and field security as declarations, not middleware. Mark tenant-owned models Contracts\BelongsToTenant and the source TenantScoped; reads scope to the current tenant, creates stamp it, and request input can never set it. Missing tenant fails closed with a 403 — which is the behaviour you want, but see Gotchas. Related records are scoped too via Tenancy\RelationScope, so eager loads, includes, relation aggregates, relation filters and relation search cannot leak across tenants. Reserve QueryContext::forTenant() and acrossTenants() for trusted code (jobs, admin tooling, imports). For column-level rules use Security\FieldAccess with false, a Gate ability, or a closure — dotted paths like FieldAccess::hidden('manager.salary') cover relation fields.

Caching and profiling per call site. Wrap hot sources in Caching\Cached with User, Tenant or Shared isolation. Read DataResult->cache() and DataResult->profile() to confirm hits and query counts. Enable QueryContext::profiled() in local/staging (or performance.profile) to get statement recording, N+1 detection and slow-call logging without leaking bindings into logs.

Gotchas and Tips

  • Everything about the 0.x declaration API is gone. new Source(...), Source::model/table/builder/collection(), Property, Support\Callback, Paginate, Sort/Sortable, Search, With, Includable, ->named() — all removed. The FilterOperator/ValueType enums and CustomOperator class are replaced by Contracts\Operator and Contracts\FilterValueType, with Contracts\CustomOperator for custom operators. Budget real time for docs/migration.md; this is a rewrite, not a rename.
  • SourceDefinition/SourceCompiler moved to Compilation\. Any code importing from Definition\ will fatal at boot — grep for it first.
  • Filter/operator mismatches fail at definition time. Declaring Integer with contains, or listing an operator the type doesn't support, blows up when the source is built. Keep a test that instantiates every source; it catches declaration errors before a request does.
  • Implicit equality is easy to lose. Add an operator and Equal is no longer assumed. If a filter stops matching on exact values after you add Contains, this is why.
  • configurations() now returns a list. Use configuration(Filter::class) and hasConfiguration(Filter::class) rather than indexing by key. validate() likewise returns a list of messages — render them all.
  • Hard caps are not configurable from the notes: relation depth is three, search spans at most twenty fields. Design nested includes around that instead of fighting it — flatten into a dedicated source if you need deeper graphs.
  • Lazy loading is prevented on returned models. Touching an undeclared relation throws instead of silently querying. That's a good default after you've migrated, but existing blade/resource code that lazy-loads off returned models will break; declare an Includable or eager-load explicitly.
  • Generic error bodies hide the cause. 404, 403 (missing tenant) and 503 (timeout) responses carry deliberately vague messages; source names, timeout values and hints live in context(). Log context() and make sure your exception handler does too, or you will debug blind.
  • Cache invalidation only watches gcego/data writes and Eloquent model events. Raw query-builder writes, other services and artisan imports bypass it — call Data::invalidate() there. Also remember tenant-scoped sources are keyed per tenant even under CacheIsolation::Shared, and that authorization, field rules and transforms still execute for every caller on a cache hit (a cache hit is not an auth bypass, and it isn't free either).
  • Writes that would leave the scope are rolled back, not silently clamped. Updating a constrained column or a tenant key throws MutationException; input can never set the tenant column. Treat those exceptions as a signal that a field shouldn't have been in the allowlist.
  • Statement timeouts are driver-limited (MySQL/MariaDB, PostgreSQL). SQLite gets no timeout, so a timeout-dependent test suite will not reproduce in CI if you test on SQLite.
  • Pagination offset and collection item limits will bite exports. Both are enforced caps; for large exports and reporting, use Data::aggregate() or scopedQuery() rather than deep-paging.
  • Custom providers must update signatures. Contracts\ResolvesRecords::find() gained a fifth $aggregates parameter — every custom implementation needs it. Likewise, SourceConfiguration::normalize(): array is declared locally now, not inherited from a components contract.
  • Extension points worth knowing: Contracts\SourceProvider, Operator, CustomOperator, FilterHandler, ProvidesScopedQueries, SupportsMutations, SupportsAggregation, TenantResolver, ResolvesRecords. scopedQuery() is the right escape hatch for packages that must aggregate in the database themselves (analytics) while inheriting view authorization, mandatory constraints, tenancy and allowlisted filters.
  • Integration\SourceRenderer, blueprints and registry integration are removed. Rendering is now the application's job; if you relied on BlueprintRole/BlueprintPart or DataManager::sourceOf(), that code must be replaced, not ported.
  • Prefer DI of DataManager over the Data facade in package/library code even though the facade is auto-aliased — it keeps tests explicit about tenant and profiling contexts.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource