From 054e0d681b5f47188c8db9caba8abba0fd4ebecc Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Thu, 11 Apr 2024 14:25:17 +0200 Subject: [PATCH] Add reporter for `Model::preventAccessingMissingAttributes()` (#824) --- src/Sentry/Laravel/Integration.php | 53 +++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Laravel/Integration.php b/src/Sentry/Laravel/Integration.php index 3a7ee025..61e7e037 100644 --- a/src/Sentry/Laravel/Integration.php +++ b/src/Sentry/Laravel/Integration.php @@ -2,6 +2,7 @@ namespace Sentry\Laravel; +use Illuminate\Database\Eloquent\MissingAttributeException; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\LazyLoadingViolationException; use Illuminate\Foundation\Configuration\Exceptions; @@ -240,6 +241,55 @@ public static function captureUnhandledException(Throwable $throwable): ?EventId return SentrySdk::getCurrentHub()->captureException($throwable, $hint); } + /** + * Returns a callback that can be passed to `Model::handleMissingAttributeViolationUsing` to report missing attribute violations to Sentry. + * + * @param callable|null $callback Optional callback to be called after the violation is reported to Sentry. + * + * @return callable + */ + public static function missingAttributeViolationReporter(?callable $callback = null): callable + { + return new class($callback) { + use ResolvesEventOrigin; + + /** @var callable|null $callback */ + private $callback; + + public function __construct(?callable $callback) + { + $this->callback = $callback; + } + + public function __invoke(Model $model, string $attribute): void + { + SentrySdk::getCurrentHub()->withScope(function (Scope $scope) use ($model, $attribute) { + $scope->setContext('violation', [ + 'model' => get_class($model), + 'attribute' => $attribute, + 'origin' => $this->resolveEventOrigin(), + 'kind' => 'missing_attribute', + ]); + + SentrySdk::getCurrentHub()->captureEvent( + tap(Event::createEvent(), static function (Event $event) { + $event->setLevel(Severity::warning()); + }), + EventHint::fromArray([ + 'exception' => new MissingAttributeException($model, $attribute), + 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true), + ]) + ); + }); + + // Forward the violation to the next handler if there is one + if ($this->callback !== null) { + call_user_func($this->callback, $model, $attribute); + } + } + }; + } + /** * Returns a callback that can be passed to `Model::handleLazyLoadingViolationUsing` to report lazy loading violations to Sentry. * @@ -263,7 +313,7 @@ public function __construct(?callable $callback) public function __invoke(Model $model, string $relation): void { // Laravel uses these checks itself to not throw an exception if the model doesn't exist or was just created - // See: https://github.com/laravel/framework/blob/438d02d3a891ab4d73ffea2c223b5d37947b5e93/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L563 + // See: https://github.com/laravel/framework/blob/438d02d3a891ab4d73ffea2c223b5d37947b5e93/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L559-L561 if (!$model->exists || $model->wasRecentlyCreated) { return; } @@ -273,6 +323,7 @@ public function __invoke(Model $model, string $relation): void 'model' => get_class($model), 'relation' => $relation, 'origin' => $this->resolveEventOrigin(), + 'kind' => 'lazy_loading', ]); SentrySdk::getCurrentHub()->captureEvent(