Summary
All 84 concrete audit log formatters in app/Audit/ConcreteFormatters/ use catch (\Exception $ex) to suppress errors. In PHP 8, TypeError and Error (e.g., thrown by Doctrine when accessing a lazy-loaded association on a detached entity) extend \Error, not \Exception. These will escape the catch block — causing the audit entry to be silently lost with no trace in the logs.
PR #499 surfaced this pattern through code review, but the issue is not new or isolated to that PR — it is a codebase-wide gap present in every formatter since the audit subsystem was introduced.
Scope
app/Audit/ConcreteFormatters/ # 84 files affected
├── PresentationFormatters/ # 10 formatters
├── ChildEntityFormatters/ # 2 formatters
└── *.php # 72 formatters
Run to confirm full scope:
grep -rl 'catch (\\Exception' app/Audit/ConcreteFormatters/ | wc -l
# => 84
Failure scenario
- An audit event fires for any entity.
- The formatter accesses a Doctrine lazy-loaded association (e.g.,
$subject->getSummit(), $subject->getLocation(), $subject->getConfig()) on a detached or expired entity.
- Doctrine throws a
\Doctrine\ORM\EntityNotFoundException (caught — it extends \RuntimeException) or a TypeError / \Error (NOT caught — extends \Error, not \Exception).
- The exception propagates;
AuditEventListener logs and swallows it — the audit log entry is permanently lost.
Fix
In every formatter's format() method, change:
} catch (\Exception $ex) {
to:
} catch (\Throwable $ex) {
This is a one-word change per file, consistent with PHP 8 best practice. Can be applied in bulk:
find app/Audit/ConcreteFormatters -name '*.php' -exec sed -i 's/catch (\\Exception \$ex)/catch (\\Throwable $ex)/g' {} +
Verify afterward that no formatter has any remaining catch (\Exception):
grep -r 'catch (\\Exception' app/Audit/ConcreteFormatters/
References
Summary
All 84 concrete audit log formatters in
app/Audit/ConcreteFormatters/usecatch (\Exception $ex)to suppress errors. In PHP 8,TypeErrorandError(e.g., thrown by Doctrine when accessing a lazy-loaded association on a detached entity) extend\Error, not\Exception. These will escape the catch block — causing the audit entry to be silently lost with no trace in the logs.PR #499 surfaced this pattern through code review, but the issue is not new or isolated to that PR — it is a codebase-wide gap present in every formatter since the audit subsystem was introduced.
Scope
Run to confirm full scope:
Failure scenario
$subject->getSummit(),$subject->getLocation(),$subject->getConfig()) on a detached or expired entity.\Doctrine\ORM\EntityNotFoundException(caught — it extends\RuntimeException) or aTypeError/\Error(NOT caught — extends\Error, not\Exception).AuditEventListenerlogs and swallows it — the audit log entry is permanently lost.Fix
In every formatter's
format()method, change:to:
This is a one-word change per file, consistent with PHP 8 best practice. Can be applied in bulk:
Verify afterward that no formatter has any remaining
catch (\Exception):grep -r 'catch (\\Exception' app/Audit/ConcreteFormatters/References
\Throwableis the root;\Errorand\Exceptionare direct children —catch (\Exception)does not catch\Errorsubclasses.