byrcsc / laravel-checklist
Run versioned Laravel checklists from template authoring through completion and review, with evidence, scoring, reports, and a tamper-evident audit trail.
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/console: ^12.0||^13.0
- illuminate/contracts: ^12.0||^13.0
- illuminate/database: ^12.0||^13.0
- illuminate/events: ^12.0||^13.0
- illuminate/filesystem: ^12.0||^13.0
- illuminate/http: ^12.0||^13.0
- illuminate/notifications: ^12.0||^13.0
- illuminate/support: ^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.3.1
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
Vehicle checks, site inspections, maintenance jobs, opening and closing routines: work that records answers to a fixed list of questions, and that somebody may have to account for months later.
Laravel Checklist runs that work from template authoring through completion and review, with evidence, scoring, reports, and a tamper-evident audit trail. Publishing a template freezes its questions, so a checklist answered last year still shows the questions it was actually asked.
The package supplies the engine and the Eloquent models. It has no user interface and registers no routes.
Read the documentation for the full reference. This README covers installation and the main flows.
| Laravel | Tested PHP versions |
|---|---|
| 12.x | 8.3, 8.4 |
| 13.x | 8.3, 8.4 |
Tested on MySQL and PostgreSQL.
Installation
composer require byrcsc/laravel-checklist
php artisan vendor:publish --tag="checklist-migrations"
php artisan migrate
Publish the configuration before migrating if you need custom table names or non-integer user identities:
php artisan vendor:publish --tag="checklist-config"
Notification wording publishes separately, and only if you want to change it:
php artisan vendor:publish --tag="checklist-translations"
Every configuration key is documented in Configuration.
Key terms
A template publishes immutable versions holding sections and items. A checklist is one run of a version, optionally about a subject such as a vehicle. A response holds the answer to one item, and every change is recorded in an append-only audit trail.
Key terms covers the rest.
Item types
The set below is fixed in v1. Validation settings live on the item, and each type decides how its answer is validated, stored, and scored.
| Type | Config | Stored as | Score |
|---|---|---|---|
checkbox |
none | boolean | checked 1, unchecked 0 |
yes_no |
none | boolean | yes 1, no 0 |
pass_fail |
none | boolean | pass 1, fail 0 |
text |
max_length |
string | unscored |
number |
min, max |
float | unscored |
rating |
min, max (both required, min < max) |
int | normalized from 0 to 1 |
date |
none | Y-m-d string |
unscored |
select |
options, multiple |
option value, or a list of them | the option's score, or their mean |
Single and multiple select share one item type, separated by the multiple
flag rather than by a type of their own.
An answer fails when its score is exactly 0. The same definition decides
whether the item-failed notification is sent and whether a critical item fails
the checklist, so the two always agree. Unscored answers never fail, and an
unanswered item is not a failed one.
Word yes/no questions so that the passing answer scores. "Any body damage?" scores an undamaged vehicle at
0and reports it as a failure, because the accurate answer is no. "Is the bodywork free of damage?" scores an undamaged vehicle at full weight. Nothing in the package enforces this. The scoring is correct either way; only the second question maps a good vehicle to a passing score.
Full details in Items and sections.
Quick start
Author and publish a template with the builder. It validates the whole template before writing anything: rule targets must exist, branches must not be orphaned, and the section tree must stay inside the depth limit.
use ByRcsc\LaravelChecklist\Authoring\TemplateBuilder; $version = TemplateBuilder::make('Vehicle pre-trip inspection') ->passThreshold(80) ->section('Exterior', function (TemplateBuilder $builder): void { $builder->passFail('Tires are roadworthy')->critical()->weight(2); $builder->passFail('No fluid leaks underneath') ->requireEvidenceWhen('fail', 'photo'); $builder->yesNo('Bodywork is free of damage?'); }) ->section('Cabin', function (TemplateBuilder $builder): void { $builder->checkbox('Registration and insurance present'); $builder->rating('Interior cleanliness', min: 1, max: 5); }) ->publish();
Nothing is written until publish(), and it writes everything in one
transaction. A template that fails validation leaves no rows behind.
To edit a published template, draft the next version:
$draft = $version->newDraft(); // deep copy as version N+1, lineage carried $draft->items()->where(...)->first()->update(['label' => 'Reworded']); $v2 = $draft->publish(); // version N is left exactly as it was
Give any model checklists:
use ByRcsc\LaravelChecklist\Concerns\HasChecklists; class Vehicle extends Model { use HasChecklists; }
Run one:
$checklist = $version->start(subject: $vehicle, assignedTo: $driver); $checklist->answer($item, 'fail'); // validated and cast by the item's type, // and auto-transitions to in_progress $checklist->addEvidence($response, $uploadedFile, 'photo'); // via Storage $checklist->submit(); // refuses while anything required is unanswered, // reporting everything outstanding at once $checklist->review($inspector, outcome: 'accepted');
A checklist moves through pending, in_progress, completed, and
reviewed. The first answer starts it. You can reopen it from completed or
reviewed, which clears the review and is recorded in the audit trail. Any
other transition throws.
Work through a full run in the Quick start.
What the package does
Rules. Two
kinds, both written against the item whose answer decides them. Requirement
rules (requireEvidenceWhen(), requireNoteWhen()) ask for more than an
answer. They are reported when the answer is given and enforced at submit().
Visibility rules (showWhen(), hideWhen()) change which questions apply, and
hiding a section hides everything inside it. If a show rule and a hide rule both
apply, the item is hidden. Hidden answers and their evidence are kept and marked
not applicable, so they are left out of scoring, progress, and completion
checks. Reopen the branch and the earlier answer is still there.
Scoring. Each
scorable answer maps to a value from 0 to 1, and each item can carry a
weight. The checklist score is the earned weight divided by the weight of the
items that applied, so a hidden question lowers the total the score is measured
against instead of counting as a failure. Section scores and the total are
calculated once at submission and stored, so a report run years later returns
what was recorded then. A checklist with nothing scorable has no score at all,
which is not the same as a score of 0. A failed critical item fails the
checklist whatever the score.
Evidence. Photos, files, and signatures attach to an answer and are written through Laravel Storage, with no media-library dependency. Each row records the disk, path, MIME type read from the file contents, size, SHA-256, and who captured it when. Files are stored under a new ULID, and extensions that a web server might execute are dropped. The package writes and deletes files. Your application decides who may see one and builds the URL.
Recurrence. A schedule pairs a template with a frequency, a timezone, and optionally a subject and an assignee. Daily, weekly, monthly, and every-N-days are supported; iCal RRULE expressions are not. One command creates the checklists that are due:
Schedule::command('checklist:generate-due')->everyFifteenMinutes();
Each due date is calculated in the schedule's own timezone from its own
next_due_at, so running the command late does not shift the schedule. However
long the command has been down, a schedule creates at most one catch-up
checklist.
Events and
notifications.
Every transition fires an event, and each one implements
ShouldDispatchAfterCommit, so it is safe to wrap a checklist operation in your
own transaction. The package also ships mail and database notifications for
every lifecycle event, which most engine packages leave to the application. They
are included because an unassigned or overdue checklist usually needs someone
told before it gets done. One RecipientResolver class decides who is notified,
and turning the notifications off leaves the events running so you can send your
own.
Audit trail.
Every change is recorded in an append-only checklist_actions table, both per
item and per checklist. Rows cannot be updated or deleted, and one class writes
all of them. Each row's SHA-256 covers its own contents and the hash of the row
before it, and the checklist stores the latest hash.
php artisan checklist:verify # exits non-zero if any chain is broken
This detects an edited row, a deleted row, and a row inserted in the middle. It does not stop someone who can already write to the table: the hash uses no secret, so that person can recalculate the whole chain and verification will pass. Treat it as a strong check against mistakes and casual tampering, not as a cryptographic guarantee.
Export and
reports. The
package generates no PDFs; it assembles the data and documents how to render it.
$checklist->toExport() returns the whole checklist in reading order. Scores
come from what was stored at submission and are never recalculated, and answers
a branch set aside are included and flagged rather than dropped. Eleven query
scopes and five reports read the stored results. mostFailedItems() groups by
lineage ULID, so rewording a question does not restart its count.
Composing with sibling
packages.
This package depends on none of its siblings. byrcsc/laravel-comments,
byrcsc/laravel-approval, and byrcsc/laravel-assignment attach through
polymorphic relations for discussion, multi-stage sign-off, and assignment
routing.
Not included in v1
No user interface. No authorization, file URLs, or thumbnails. No PDF rendering, though the documentation includes a Blade and dompdf recipe. No threaded comments, assignment routing, or multi-stage approval; the sibling packages above cover those. No iCal RRULE recurrence. No repeatable sections, such as one copy per room. No custom item types, though the internal type handlers mean a public registry could open in a later minor release. No cached or materialized statistics tables.
Testing
composer test # the suite composer analyse # PHPStan, level max composer format # Pint
The workbench/ directory holds a demo application that installs the package
the way a real application does and drives one checklist through every feature:
composer build # migrate and seed from clean php vendor/bin/testbench checklist:demo # the whole engine, narrated
It exits non-zero if no notification was sent or an audit chain fails to verify,
and CI runs it on every push. workbench/README.md lists
what it seeds and which JSON routes it serves. It is the quickest way to see how
an integration fits together before writing your own.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for how to set up, the three checks every pull request must pass, where tests go, and what falls outside the package's scope. Everybody taking part is expected to follow the Code of Conduct.
Security
Please review our security policy on how to report security vulnerabilities. Do not open a public issue for one.
Credits
License
The MIT License (MIT). Please see License File for more information.