Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ private function hookDestroy(): void
hook(
EloquentModel::class,
'destroy',
pre: function ($model, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
pre: function (string $modelClassName, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
// The class-string is passed to the $model argument, because \Illuminate\Database\Eloquent\Model::destroy is static method.
// Therefore, create a class instance from a class-string, and then get the table name from the getTable function.
$model = new $modelClassName();

$builder = $this->instrumentation
->tracer()
->spanBuilder($model::class . '::destroy')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,16 @@ public function test_eloquent_operations(): void
try {
// Test create
$created = TestModel::create(['name' => 'test']);

// Test find
$found = TestModel::find($created->id);

// Test update
$found->update(['name' => 'updated']);

// Test delete
$found->delete();

return response()->json(['status' => 'ok']);
} catch (\Exception $e) {
return response()->json([
Expand All @@ -127,18 +127,7 @@ public function test_eloquent_operations(): void
}
$this->assertEquals(200, $response->status());

// Verify spans for each Eloquent operation
/** @var array<int, \OpenTelemetry\SDK\Trace\ImmutableSpan> $spans */
$spans = array_values(array_filter(
iterator_to_array($this->storage),
fn ($item) => $item instanceof \OpenTelemetry\SDK\Trace\ImmutableSpan
));

// Filter out SQL spans and keep only Eloquent spans
$eloquentSpans = array_values(array_filter(
$spans,
fn ($span) => str_contains($span->getName(), '::')
));
$eloquentSpans = $this->resolveEloquentOperationSpans();

// Sort spans by operation type to ensure consistent order
usort($eloquentSpans, function ($a, $b) {
Expand Down Expand Up @@ -186,6 +175,58 @@ public function test_eloquent_operations(): void
$this->assertSame('delete', $deleteSpan->getAttributes()->get('laravel.eloquent.operation'));
}

public function test_eloquent_static_operations(): void
{
// Assert storage is empty before interacting with the database
$this->assertCount(0, $this->storage);

// Create the test_models table
DB::statement('CREATE TABLE IF NOT EXISTS test_models (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
created_at DATETIME,
updated_at DATETIME
)');

$this->router()->get('/eloquent', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to use the HTTP kernel to test model functionality? 🤔

Copy link
Contributor Author

@rozeo rozeo Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for review!

The main reason is to test in the same way as much as possible, because test cases with similar intentions were defined in other cases and added as additional cases, but as you reviewed, the TraceProvider setting is done on the TestCase class, so it seems that the test itself is possible without going through the HTTP kernel.
However, it is defined as an integration test, I personally think the current way makes more sense, since the test is to be conducted in the same way as the actual use case and a database is required in this case.
But a little redundant.

If a better testing method is exists, I will be create a new PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rozeo - we can split these out later then 👍

try {
// create record for Test destroy
$created = TestModel::create(['name' => 'test']);

// Test destroy
TestModel::destroy([$created->id]);

return response()->json(['status' => 'ok']);
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
], 500);
}
});

$response = $this->call('GET', '/eloquent');
if ($response->status() !== 200) {
$this->fail('Request failed: ' . $response->content());
}
$this->assertEquals(200, $response->status());

$eloquentSpans = $this->resolveEloquentOperationSpans();

// Destroy span
$destroySpans = array_values(array_filter($eloquentSpans, function ($span) {
return $span->getAttributes()->get('laravel.eloquent.operation') === 'destroy';
}));

$this->assertCount(1, $destroySpans);

$destroySpan = $destroySpans[0];
$this->assertSame('OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Fixtures\Models\TestModel::destroy', $destroySpan->getName());
$this->assertSame('OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Fixtures\Models\TestModel', $destroySpan->getAttributes()->get('laravel.eloquent.model'));
$this->assertSame('test_models', $destroySpan->getAttributes()->get('laravel.eloquent.table'));
$this->assertSame('destroy', $destroySpan->getAttributes()->get('laravel.eloquent.operation'));
}

public function test_low_cardinality_route_span_name(): void
{
$this->router()->get('/hello/{name}', fn () => null)->name('hello-name');
Expand Down Expand Up @@ -213,4 +254,25 @@ private function router(): Router
/** @psalm-suppress PossiblyNullReference */
return $this->app->make(Router::class);
}

/**
* @return array<int, \OpenTelemetry\SDK\Trace\ImmutableSpan>
*/
private function resolveEloquentOperationSpans(): array
{
// Verify spans for each Eloquent operation
/** @var array<int, \OpenTelemetry\SDK\Trace\ImmutableSpan> $spans */
$spans = array_values(array_filter(
iterator_to_array($this->storage),
fn ($item) => $item instanceof \OpenTelemetry\SDK\Trace\ImmutableSpan
));

// Filter out SQL spans and keep only Eloquent spans
$eloquentSpans = array_values(array_filter(
$spans,
fn ($span) => str_contains($span->getName(), '::')
));

return $eloquentSpans;
}
}