jacobjoergensen/laravel-paper
Laravel Paper adds flat-file drivers to Eloquent for Laravel 12+ (PHP 8.4+). Point a model to a content directory and query Markdown or JSON files with familiar Eloquent APIs—no database, schema, or custom connection. Uses attributes + a trait.
whereNotLike and orWhereNotLike, the negated form of whereLikeunless, the counterpart to when, running its callback when the value is falsywhere to treat a column named after a PHP function, like date, as a column instead of a closurewhere and orWhere to bind and tighter than or like SQL; a chain mixing the two silently dropped matching recordswhere with a null value to check the column for null, like Eloquent; it matched nothing beforefind to apply the query's pending where constraints, so a filtered query no longer returns an excluded recordwhereBetween and whereNotBetween to read the bounds by position like Laravel, so an array with string keys no longer raises a PHP errorsave writing null over an array, json, object, or collection field whose file value the cast could not read#[Timestamps] overwriting a frontmatter field with the file mtime when UPDATED_AT names a real field, and stripping that field from the file on saveJsonDriver to leave forward slashes unescaped, so saving a record no longer rewrites every URL in the fileMarkdownDriver to throw FileParseException for malformed frontmatter instead of a raw Symfony exception, so the error names the fileFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.14.0...1.15.0
laravel-paper-development skill to cover lazy loading, route model binding, and driver extension order#[Driver] and #[ContentPath] to resolve on first use instead of when the model is instantiatedFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.13.0...1.14.0
composer bench) measuring query performance across directory sizeschunk and each to process records in batches over the lazy iteratorfirstOrNew to return the first matching record or a new unsaved instancefindOr and firstOr to run a callback when no record matchespluck to key the results by a second columnPaperRule exists and unique messages to use Laravel's validation translation lines so they respect the app localepaginate and simplePaginate to read only the current page's files when the query has no where clause and isn't ordered by a frontmatter fieldupdated_at to read each content file's modification time once instead of twicewhereNotBetween to exclude records whose column is missing, matching whereNotInFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.12.0...1.13.0
min, max, sum, avg, and average aggregate methods, skipping null and non-numeric values like SQL aggregates skip NULL{model} and {model:field} resolve the matching record; scoped child bindings now throw UnsupportedRouteBindingExceptionlaravel-paper-development to give AI agents Paper-specific guidanceorderBy to treat the first column as primary and later columns as tiebreakers, matching EloquentwhereIn and whereNotIn to compare loosely like where, so '1' matches an integer 1 read from frontmatterFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.11.0...1.12.0
This release is backward compatible, but two things worth knowing before you upgrade:
retrieved now fires when a query returns a model, the same way it does in Eloquent. You should only notice this if you already have a retrieved listener.#[Timestamps] attribute to expose a model's file modification time as updated_at; created_at stays a frontmatter fieldretrieved model event, fired for each model a query returns and skipped on count, exists, pluck, and bulk deleteupdate to set values across all matching recordsfindMany for loading multiple records by slug in one callorderByDesc as a descending order shortcutFileModificationCache with an in-process memo to avoid repeated cache lookups within the same requestMarkdownDriver serialization to keep nested frontmatter in block style for cleaner diffsMarkdownDriver to omit the frontmatter block for content-only models instead of writing an empty { } blockFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.10.0...1.11.0
#[Scope] attribute, including protected methods#[CollectedBy] attribute so queries return the model's custom collectioncreate for saving a new record from an attribute arrayfirstOrCreate to return the first matching record or create itupdateOrCreate to update the first matching record or create itsaveQuietly to persist a record without firing model eventsdeleteQuietly to delete a record without firing model eventsarray, json, object, and collection casts so they read from and write to flat files as native structuresFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.9.0...1.10.0
DriverRegistry so custom drivers can be registered with register()PaperException interface implemented by all package exceptionsPaperUniqueRule::ignore for excluding a record by a column other than the slugsimplePaginate to paginate without counting every record, reading only the rows the page needsinRandomOrder for returning records in random ordervalue for reading a single column from the first matchfirstWhere to fetch the first record matching a where conditionwhen for applying query clauses conditionallywhereAny, orWhereAny, whereAll, and orWhereAll for matching a value across multiple columnswhereLike and orWhereLike with an optional case-sensitive flag, matching Laravel semanticsdelete to mark the model as no longer existing, matching Eloquentfind, save, and delete by validating the slug is a single safe filename segmentsave to accept "0" as a slug instead of rejecting it as emptysave to create the content directory when it is missing instead of failing silentlysave to overwrite the existing file's extension on update instead of writing a duplicate .md next to a .markdownsave to sync model state so isDirty, wasChanged, and wasRecentlyCreated are correct afterwardFull Changelog: https://github.com/JacobJoergensen/laravel-paper/compare/1.8.0...1.9.0
all, find, findOrFail, and freshlazy lists files up fronthasManyPaper reads every related file on each callwhere comparison operators to exclude null fields, matching SQL semanticssave to write atomically via temp file + renamepaginate to resolve current page and path via Paginatorexists and doesntExist static methods to the Paper traitcount to skip file parsing when no where clauses are appliedexists and doesntExist to short-circuit on first matchsole query method for retrieving exactly one recordfirst to use early termination on unordered querieslazy to pass callable instead of instantiated generator__call on PaperQueryBuilderbelongsToPaper for O(1) relationship lookupshasManyPaper for one-to-many relationshipslazy method for memory-efficient iteration with LazyCollectionPaperRule validation with exists and unique rules for Paper modelsfresh and refresh methods for reloading models from filedelete on query builderexists and doesntExist query methodswhere with string values not working correctlywhere(closure) and orWhere(closure) for grouped conditionswhereContains and orWhereContains for array column filteringwhereNull, orWhereNull, whereNotNull and orWhereNotNull for null checkswhereBetween, orWhereBetween, whereNotBetween and orWhereNotBetween for range querieslatest and oldest ordering shortcutsfirstOrFail query methodpaginate for pagination supportcount, pluck, orWhere, whereIn, orWhereIn, whereNotIn and orWhereNotIn query methodssave and delete methods for write supportcreating, created, updating, updated, saving, saved, deleting and deleted)Initial release
How can I help you explore Laravel packages today?