karim-tao/laravel-violation-logger
A Laravel package for logging violations and policy breaches with structured context, helping you capture, store, and review issues for auditing, debugging, and operational visibility.
This package reports validation violations as a nested JSON structure. As of 0.2.0 the shape is:
{
"<subject>": {
"<rule or violation type>": {
"<detail>": <count>
}
}
}
Level 1 groups by subject (file, model, request, whatever the package is inspecting), level 2 by rule/category, and level 3 is now a map of detail → occurrence count rather than a list of repeated details.
Minimal first steps:
composer require the package, then publish its config (php artisan vendor:publish --tag=...) and read that file top to bottom — it's where the subject/rule grouping and output format are decided.json_encode of it. Confirming the third level is an object, not an array, on day one saves you a debugging session later.First real use case: take a payload, index level 3 by key to get the distinct details, and sum the values to get total occurrences:
$violations = $reporter->violations(); // nested array
foreach ($violations as $subject => $rules) {
foreach ($rules as $rule => $details) {
$total = array_sum($details);
foreach ($details as $detail => $count) {
// "$detail occurred $count time(s) under $rule"
}
}
}
Rendering counts in output. Anything user-facing (CLI tables, PR annotations, dashboards) now wants "{$detail} (x{$count})" for $count > 1 and the bare detail otherwise. Centralise that in a single formatter rather than sprinkling conditionals through your views.
Sorting for stable output. Maps preserve insertion order, which is whatever order the rules happened to hit. If you snapshot this JSON in tests or post it to an API, sort explicitly — by count descending, then detail alphabetically — so diffs stay meaningful.
Aggregating across subjects. The new counts make roll-ups cheap: array_sum over level 3 gives per-rule totals, and a collect()->flatMap() over subjects gives a global "worst offenders" list without re-running the check.
Assertions in tests. Prefer asserting on specific counts (assertSame(3, $violations['x']['rule']['detail'])) over assertCount on a details list. The count is now the contract; the old list length was incidental.
Integration with Laravel's own machinery. If you surface this through an API resource or a custom validation rule, wrap the payload in a resource class so the shape is defined in one place. When the next structural change lands, you edit one file.
Custom formatters / extension points. If the package exposes a formatter or serializer interface, implement it rather than post-processing the array — you get the counts at the point they're authoritative and avoid a second pass over the tree.
Bridging to the old shape. If downstream consumers still expect lists, expand on the way out:
$legacy = [];
foreach ($details as $detail => $count) {
$legacy = array_merge($legacy, array_fill(0, $count, $detail));
}
Keep that adapter at the boundary, not inside your business logic.
in_array($detail, $level3) or $level3[0] is now wrong — it's array_key_exists($detail, $level3) and array_keys($level3)[0].[], not {}. PHP's empty array is json_encoded as a JSON array. If a client validates "level 3 is an object", cast with (object) or JSON_FORCE_OBJECT before encoding, or the empty case will fail schema validation while every non-empty case passes.['123' => 2] in PHP is [123 => 2]; json_encode may then emit a JSON array again and silently undo the new format. If details can be numeric, prefix or cast them deliberately.$violations and json_encode($violations) when something looks off. Most of the surprises above are visible only in the encoded form.array_sum across many subjects is fine, but if you're summing into a typed DTO, use int and don't accidentally cast through a string column.composer.json ("^0.2") and read release notes before updating.How can I help you explore Laravel packages today?