All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Pre-release hardening pass from a full package audit all covered by new tests.
initializer()no longer dispatches arbitrary model methods from client filter keys. Filter keys are matched only against declared query scopes (hasNamedScope). The previousmethod_exists()fallback could invoke any public model method whose studly-cased name matched a filter key e.g.?filters={"save":{}}reachedModel::save()and attempted a write on every index request. Non-scope keys are now silently ignored.updateColumnis restricted to an allowlist. The client-controlled{column}route segment previously accepted any fillable column (e.g.is_admin,role_id,email_verified_at), bypassing the update FormRequest entirely. A newprotected array $updatableColumns = ['status']gates the endpoint and returns 403 otherwise. Breaking: to update columns other thanstatus, add them to$updatableColumnson the controller.rowsPerPage=0no longer returns the whole table by default.pagination.allow_allnow defaults tofalse, so?rowsPerPage=0falls back todefault_per_pageinstead of returning an unbounded, unauthenticated result set (a DoS vector). Whenallow_allis enabled, the newpagination.max_all(default1000;0= unbounded) caps the "show all" path. Breaking: if you relied onrowsPerPage=0returning all rows, setpagination.allow_alltotrue.BaseWebControllerno longer leaks raw exception messages. Unexpected write failures are logged viareport()and shown as a generic, overridable message (genericErrorMessage()) unlessAPP_DEBUGis on preventing SQL/schema disclosure via flash messages.parseTimeToSeconds()computed a negative value under Carbon 3 (signeddiffInSeconds); it now computes arithmetically and always returns a non-negativeint(return type narrowed fromint|floattoint).- Performance:
resolveValidatedData()memoises the table column listing (no schema metadata query per write for$guardedmodels), andmodelUsesSoftDeletes()is memoised per model class.
- Dropped support for Laravel 11. The minimum supported framework is now Laravel 12 (
illuminate/* ^12.0||^13.0,orchestra/testbench ^10.0||^11.0). PHP 8.2+ is still supported. Run Laravel 12 or 13. BaseControllerandBaseWebControllerno longer extendIlluminate\Routing\Controller. They now implementHasMiddlewaredirectly. The old$this->middleware()->only()constructor registration is removed.- Automatic permission middleware registration (
registerPermissionMiddleware()) is removed. Declare permissions explicitly by overriding the staticmiddleware()method and callingstatic::permissionMiddleware('slug'). spatie/laravel-permissionis no longer a hard dependency. It has moved tosuggest. Install it separately if you use permissions:composer require spatie/laravel-permission.paginateQuery()return type narrowed frommixedtoPaginator<int, Model>|CursorPaginator<int, Model>|Collection<int, Model>.uuid()helper now returnsstringinstead ofUuidInterface.- The
HasUuidPrimaryKeytrait is removed. It duplicated framework functionality use Laravel's first-partyIlluminate\Database\Eloquent\Concerns\HasUuids(UUID v7, recommended) orHasVersion4Uuidsinstead.
Pagination::resolveEffectivePerPage(\Closure $countFn)COUNT query is deferred and only fires when "show all" is requested (rowsPerPage=0), avoiding an extra query on every normal paginated request.- Static schema index cache in
AnonymizesOnDeleteSchema::getIndexes()is now called once per table per process instead of on every soft-delete. - Support for models using
$guarded(including$guarded = []).resolveValidatedData()now falls back to the actual table columns when$fillableis empty, instead of persisting nothing. Route::fastApiResource('posts', PostController::class)macro registers the full route set (index, store, show, update, destroy, bulk delete, restoreAll, changeStatus, updateColumn, restore, permanentDelete) in one line, withonly/except/parameter/namesoptions mirroring Laravel'sapiResource.- Client-driven eager loading set
$allowedIncludeson a controller and clients request relations per call via anincludekey inside the filters JSON:?filters={"include":"author,tags"}(string or array form). Restricted to the allowlist (anything else is ignored), applied on both index and show. - Soft-delete filtering on index set
$allowTrashedFilter = trueand clients pass atrashedkey inside the filters JSON:?filters={"trashed":"with"}or{"trashed":"only"}. Opt-in and gated on the model being soft-deletable. - Bulk-delete cap
fast-api.bulk.max_rows(default 1000) bounds how many IDs the bulk delete endpoint accepts; set to 0 to disable. The request field name is configurable viafast-api.bulk.field(defaultdelete_rows). - Fully configurable query-parameter keys
fast-api.query.*lets you rename every key the index/show reads (filters,search,sortBy,descending,rowsPerPage,page,cursor, and the in-filtersinclude/trashed). Defaults are unchanged. Centralised in a newAnil\FastApiCrud\Support\QueryParamsresolver used by the macros, pagination helper, and controllers. - Configurable default sort
fast-api.sorting.default_column(defaultid) andfast-api.sorting.default_descending(defaulttrue) control ordering when the request has no sort key and the model isn'tSortable. LICENSEfile (MIT).CHANGELOG.md.
store/update/destroy/etc. on a missing record now return 404, not 400. The API controller no longer wrapsModelNotFoundException(and every other exception) into a generic 400 with a leaked message. Validation, authorization, not-found, and unexpected errors now propagate to the framework's exception handler and render with correct, debug-aware status codes (422/403/404/500).changeStatusnow toggles correctly for boolean and string ("0"/"1") casts, not just integer1/0.BaseWebControllerno longer flattensValidationExceptioninto a flash message validation errors and old input are preserved on redirect-back, and missing records render a 404.- Transaction rollback in the
perform*methods now triggers on anyThrowable(includingError/TypeError), not onlyException. ReplicatesWithRelationsis now actually usable for relation graphs it had three latent bugs that surfaced the moment it touched the database: (1)HasMany/HasOnechildren were saved before the parent foreign key was set, breaking on non-nullable FKs; children are now persisted through the parent relation; (2)reApplyCasts()copied the primary key and timestamps onto the replica (becausegetCasts()reportsid => int), undoingreplicate()'s exclusion; (3) cross-model recursion called aprivatemethod on a different model class, throwingBadMethodCallException. Covered by new tests forHasMany/BelongsTo/BelongsToMany.
- API controller actions now have narrowed return types (
JsonResourceinstead ofJsonResource|JsonResponse) since the error-wrapping branch was removed. BaseWebControllerwrite actions share a singleperform()helper, removing nine duplicated try/catch blocks.MakeAllCommanduses theFilefacade (ensureDirectoryExists/put/exists) instead of rawmkdir/file_put_contents/file_exists, andhandle()now returns properSUCCESS/FAILUREexit codes.applyScopes()now usesModel::hasNamedScope()instead of manualmethod_existsdouble-check. This correctly supports scopes defined via the#[LocalScope]PHP attribute (Laravel 12+).permissionMiddleware()now includes thefast-api.permissions.enabledconfig guard and aclass_existscheck for Spatie, making it safe to call unconditionally.- Lifecycle hook methods (
beforeCreate,afterCreate,beforeUpdate,afterUpdate,beforeDelete,afterDelete,beforeStatusChange,afterStatusChange,beforeColumnUpdate,afterColumnUpdate,beforeRestore,afterRestore,beforeForceDelete,afterForceDelete) consolidated each is now a one-liner delegating to a privatefireModelHook()dispatcher. performRestore,performRestoreAll, andperformPermanentDeletenow chaininitializer()->onlyTrashed()consistently with the rest of the codebase.BaseController204 responses now callnoContent()directly instead of wrapping an empty array insuccess().Str::studly()in theinitializermacro is now cached in a local variable instead of being evaluated twice per filter.- CI matrix tests PHP 8.2–8.5 × Laravel 12–13 (both
prefer-stableandprefer-lowest). HasPermissionSlugcontract andconfig/fast-api.phpcomments updated to reflect the new explicitmiddleware()pattern.
- General stability updates and code style fixes.
withAggregatesmacro on the controller was not being applied correctly.
- General updates and code style fixes.
withCountWhereHasandorWithCountWhereHasBuilder macros combinewithCount+whereHas/orWhereHasin a single call.
equalWheremacro edge cases.initializermacro filter handling improvements.- Code style fixes.
- General updates and code style fixes.
- General updates.
- General updates.
- Code formatting and style cleanup.
- Namespace fix for the
AnonymizesOnDelete(delete event) trait.
- General updates.
tableColumns()helper now returnsidfirst and timestamp columns (created_at,updated_at,deleted_at) last, with remaining columns sorted alphabetically in between.
tableColumns()/getColumns()helper function to retrieve an ordered list of column names for a table or Eloquent model.
initializermacro now falls back tonewQuery()when the model does not define aninitializeModelmethod, preventing a fatal error on plain models.
- Config file updates.
- Argument types relaxed from
stringtomixedin several places to improve compatibility.
- Full package rewrite targeting Laravel 10+.
- Introduced
BaseControllerandBaseWebControlleras the primary extension points. initializerBuilder macro replaces the olddefaultOrderand filter helpers.paginates,simplePaginates, andcursorPaginatesBuilder macros added, powered by thePaginationsupport class.- Config file (
fast-api.php) introduced with pagination, soft-delete, response, permission, and web sections.
HasCrudOperationstrait shared CRUD query building, lifecycle hooks, and operation execution for both API and web controllers.HasApiResponsetrait typed helper methods for every HTTP status code.HasDateScopesmodel traittoday,yesterday,thisWeek,lastWeek,monthToDate,thisMonth,lastMonth,quarterToDate,yearToDate,last7Days,last30Days,lastQuarter,lastYear,datescopes.AnonymizesOnDeletemodel trait appends_{timestamp}to unique column values on soft delete to prevent constraint violations.HasUuidPrimaryKeymodel trait auto-assigns UUID v4 on creation.ReplicatesWithRelationsmodel trait deep-replicates a model along with its loaded relations to any depth, with circular reference protection.Searchable,Sortable,HasPermissionSlugcontracts.CrudActionandPaginationTypeenums.ApiExceptionrenders a structured JSON error response with optional debug detail.fast-api:make-allArtisan command scaffolds model, migration, factory, seeder, controller, resource, and requests in one step. Supports--webflag for Blade controller + view stubs.CollectionMacros::paginatepaginate an in-memory Collection.- CI matrix covering PHP 8.2/8.3 × Laravel 10/11.
- PHPStan at level 10 via Larastan.
- Namespace fix for the
HasUuidPrimaryKey(UUID) trait.
getSqlQuery()andtoRawSql()helper functions.- In-memory Collection
paginate()macro.
- Package auto-discovery configuration.
- Config file auto-merging via
mergeConfigFrom.
- Initial release.
- Core helper functions (
parseTimeToSeconds,formatDuration,diffForHumans,ymdDate,filterValue,sortBy,scopeMethods,uuid, and more). - Date scopes, UUID trait, delete event trait.
- Basic controller scaffolding.