ramadan/easy-model
ramadan/easy-model is a Laravel package that simplifies Eloquent model development with convenient helpers and utilities for common model tasks, reducing boilerplate and helping you build clean, maintainable applications faster.
Architecture fit
fromRequest(), allowedFilters(), allowedSorts(), allowedIncludes(), allowedSearch()) is an API-boundary component, not an internal helper. It sits directly on the path from untrusted HTTP input to database queries, which raises the bar for review considerably.usingModelEvents(), performInsert(), and performUpsert() are the most architecturally consequential additions. Bypassing or selectively replaying model events/observers undermines the single most reliable invariant in a Laravel codebase: that writes go through the model layer and fire consistent events. Audit trails, cache invalidation, search indexing, and notification side effects typically hang off those events.Integration feasibility
Technical risk
fromRequest() plus allowedFilters()/allowedSorts()/allowedIncludes() is a classic injection and data-exfiltration surface. The allow-list model is sound in principle, but the failure mode is severe: an unlisted filter that is not strictly rejected, a sort column that accepts a relation path, or an allowedIncludes() entry that traverses to a sensitive relation can leak data or produce unbounded joins. allowedIncludes() is particularly high-risk because it lets the client shape the query graph.performUpsert() combined with usingModelEvents() implies writes that may not fire observers. Silent divergence between "writes that fire events" and "writes that don't" is exactly the kind of defect that surfaces months later as an audit-trail gap or a stale cache.onlyTrashed(), restore(), and forceDelete() are irreversible or hard-to-reverse operations exposed through a convenience API. forceDelete() reachable from a request-driven layer is a serious blast-radius concern.toSql() and SQL inspection are a genuine improvement — teams can now see generated SQL without a debugger. That directly addresses the earlier opacity concern and should be used as a review requirement, not just a debugging aid.Key questions
fromRequest() do with an unknown filter, sort, or include — throw, ignore, or pass through? The answer determines the security posture.allowedIncludes() bind to an explicit allow-list of relation names, and can it be constrained (per-endpoint, with required parent scopes) or is it global?usingModelEvents() — which events fire, which are suppressed, and do performInsert()/performUpsert() fire creating/created/saving/saved by default or only when opted in?forceDelete() reachable through the request-driven surface, and what guards it?addWhereJsonContains() and addKeywordSearch() with dotted relation columns? toSql() output should be inspected for bindings versus interpolated values.Stack fit
performInsert(), performUpsert(), usingModelEvents(), and forceDelete() as out of scope for a pilot. Pilot the read/query surface only; the write and hard-delete additions carry risk disproportionate to their convenience value.Migration path
fromRequest() at the framework boundary until the allow-list semantics in question 3 are verified by source review. Accept request input as a validated array and pass explicit parameters instead.Compatibility
toSql() output, including bindings.Sequencing
usingModelEvents() semantics, and every place raw SQL or column names are interpolated.fromRequest()/allowedIncludes()/allowedSorts() for injection, relation traversal, and unbounded-join exposure. This is the release's highest-risk change and must not be folded into general code review.toSql().Maintenance
Support
Scaling
allowedIncludes() and dotted-relation keyword search let a client dictate join depth and shape. An unbounded include graph is a denial-of-service vector and a query-plan hazard at volume. Cap includes, require explicit registration, and test against production-scale data.addKeywordSearch() across dotted relation columns is likely to produce unindexed LIKE-style scans across joined tables. Verify the generated SQL before enabling it on any large table.Failure modes
allowedIncludes() relation traversal → unbounded join → latency incident or resource exhaustion.performUpsert() without model events → silent audit-trail gap, stale cache, missed index update.forceDelete() reachable from a convenience API → unrecoverable data loss from a single mistaken call.toSql() should be used in review to catch interpolated values.Ramp-up
allowedFilters() looks like a guardrail, and teams will treat it as one whether or not it is. Training must state plainly that allow-lists are a security control requiring review, not a convenience toggle.toSql(), changelog hygiene) but simultaneously expands the blast radius into untrusted input parsing and the write path, and deepens the overlap with a better-supported alternative. A time-boxed read-only spike is justified only after the security review and the Spatie comparison; absent a specific, measured pain point, an internal helper trait or the established package remains the better trade.How can I help you explore Laravel packages today?