Skip to content

Commit 11ae1f2

Browse files
Normalized route names when using Lumen (#535)
Co-authored-by: Alex Bouma <[email protected]>
1 parent 5170764 commit 11ae1f2

File tree

4 files changed

+156
-7
lines changed

4 files changed

+156
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add support for normalized route names when using Lumen (#449)
6+
57
## 2.11.1
68

79
- Fix deprecation notice in route name extraction (#543)

src/Sentry/Laravel/Integration.php

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,28 +145,82 @@ public static function extractNameForRoute(Route $route): string
145145
return $routeName;
146146
}
147147

148-
private static function extractNameForNamedRoute(string $routeName): ?string
148+
/**
149+
* Extract the readable name for a Lumen route.
150+
*
151+
* @param array $routeData The array of route data
152+
* @param string $path The path of the request
153+
*
154+
* @return string
155+
*/
156+
public static function extractNameForLumenRoute(array $routeData, string $path): string
157+
{
158+
$routeName = null;
159+
160+
$route = $routeData[1] ?? [];
161+
162+
// someaction (route name/alias)
163+
if (!empty($route['as'])) {
164+
$routeName = self::extractNameForNamedRoute($route['as']);
165+
}
166+
167+
// Some\Controller@someAction (controller action)
168+
if (empty($routeName) && !empty($route['uses'])) {
169+
$routeName = self::extractNameForActionRoute($route['uses']);
170+
}
171+
172+
// /someaction // Fallback to the url
173+
if (empty($routeName) || $routeName === 'Closure') {
174+
$routeUri = array_reduce(
175+
array_keys($routeData[2]),
176+
static function ($carry, $key) use ($routeData) {
177+
return str_replace($routeData[2][$key], "{{$key}}", $carry);
178+
},
179+
$path
180+
);
181+
182+
$routeName = '/' . ltrim($routeUri, '/');
183+
}
184+
185+
return $routeName;
186+
}
187+
188+
/**
189+
* Take a route name and return it only if it's a usable route name.
190+
*
191+
* @param string $name
192+
*
193+
* @return string|null
194+
*/
195+
private static function extractNameForNamedRoute(string $name): ?string
149196
{
150197
// Laravel 7 route caching generates a route names if the user didn't specify one
151198
// theirselfs to optimize route matching. These route names are useless to the
152199
// developer so if we encounter a generated route name we discard the value
153-
if (Str::contains($routeName, 'generated::')) {
200+
if (Str::contains($name, 'generated::')) {
154201
return null;
155202
}
156203

157204
// If the route name ends with a `.` we assume an incomplete group name prefix
158205
// we discard this value since it will most likely not mean anything to the
159206
// developer and will be duplicated by other unnamed routes in the group
160-
if (Str::endsWith($routeName, '.')) {
207+
if (Str::endsWith($name, '.')) {
161208
return null;
162209
}
163210

164-
return $routeName;
211+
return $name;
165212
}
166213

167-
private static function extractNameForActionRoute(string $actionName): string
214+
/**
215+
* Take a controller action and strip away the base namespace if needed.
216+
*
217+
* @param string $action
218+
*
219+
* @return string
220+
*/
221+
private static function extractNameForActionRoute(string $action): string
168222
{
169-
$routeName = ltrim($actionName, '\\');
223+
$routeName = ltrim($action, '\\');
170224

171225
$baseNamespace = self::$baseControllerNamespace ?? '';
172226

src/Sentry/Laravel/Tracing/Middleware.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,27 @@ private function hydrateRequestData(Request $request): void
180180
$route = $request->route();
181181

182182
if ($route instanceof Route) {
183-
$this->updateTransactionNameIfDefault(Integration::extractNameForRoute($route));
183+
$this->updateTransactionNameIfDefault(
184+
Integration::extractNameForRoute($route)
185+
);
184186

185187
$this->transaction->setData([
186188
'name' => $route->getName(),
187189
'action' => $route->getActionName(),
188190
'method' => $request->getMethod(),
189191
]);
192+
} elseif (is_array($route) && count($route) === 3) {
193+
$this->updateTransactionNameIfDefault(
194+
Integration::extractNameForLumenRoute($route, $request->path())
195+
);
196+
197+
$action = $route[1] ?? [];
198+
199+
$this->transaction->setData([
200+
'name' => $action['as'] ?? null,
201+
'action' => $action['uses'] ?? 'Closure',
202+
'method' => $request->getMethod(),
203+
]);
190204
}
191205

192206
$this->updateTransactionNameIfDefault('/' . ltrim($request->path(), '/'));

test/Sentry/IntegrationTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,83 @@ public function testExtractingNameForRouteWithStrippedBaseNamespaceFromAction():
162162

163163
Integration::setControllersBaseNamespace(null);
164164
}
165+
166+
public function testExtractingNameForLumenRouteWithName(): void
167+
{
168+
$route = [0, ['as' => $routeName = 'foo-bar'], []];
169+
170+
$this->assertSame($routeName, Integration::extractNameForLumenRoute($route, '/some-route'));
171+
}
172+
173+
public function testExtractingNameForLumenRouteWithAction(): void
174+
{
175+
$route = [0, ['uses' => $controller = 'SomeController@someAction'], []];
176+
177+
$this->assertSame($controller, Integration::extractNameForLumenRoute($route, '/some-route'));
178+
}
179+
180+
public function testExtractingNameForLumenRouteWithoutName(): void
181+
{
182+
$url = '/some-route';
183+
184+
$this->assertSame($url, Integration::extractNameForLumenRoute([0, [], []], $url));
185+
}
186+
187+
public function testExtractingNameForLumenRouteWithParamInUrl(): void
188+
{
189+
$route = [1, [], ['param1' => 'foo']];
190+
191+
$url = '/foo/bar/baz';
192+
193+
$this->assertSame('/{param1}/bar/baz', Integration::extractNameForLumenRoute($route, $url));
194+
}
195+
196+
public function testExtractingNameForLumenRouteWithParamsInUrl(): void
197+
{
198+
$route = [1, [], ['param1' => 'foo', 'param2' => 'bar']];
199+
200+
$url = '/foo/bar/baz';
201+
202+
$this->assertSame('/{param1}/{param2}/baz', Integration::extractNameForLumenRoute($route, $url));
203+
}
204+
205+
public function testExtractingNameForLumenRouteWithActionAndName(): void
206+
{
207+
$route = [0, [
208+
'as' => $routeName = 'foo-bar',
209+
'uses' => 'SomeController@someAction',
210+
], []];
211+
212+
$this->assertSame($routeName, Integration::extractNameForLumenRoute($route, '/some-route'));
213+
}
214+
215+
public function testExtractingNameForLumenRouteWithAutoGeneratedName(): void
216+
{
217+
// We fake a generated name here, Laravel generates them each starting with `generated::`
218+
$route = [0, ['as' => 'generated::KoAePbpBofo01ey4'], []];
219+
220+
$url = '/some-route';
221+
222+
$this->assertSame($url, Integration::extractNameForLumenRoute($route, $url));
223+
}
224+
225+
public function testExtractingNameForLumenRouteWithIncompleteGroupName(): void
226+
{
227+
$route = [0, ['as' => 'group-name.'], []];
228+
229+
$url = '/some-route';
230+
231+
$this->assertSame($url, Integration::extractNameForLumenRoute($route, $url));
232+
}
233+
234+
public function testExtractingNameForLumenRouteWithStrippedBaseNamespaceFromAction(): void
235+
{
236+
Integration::setControllersBaseNamespace('BaseNamespace');
237+
238+
$route = [0, ['uses' => 'BaseNamespace\\SomeController@someAction'], []];
239+
240+
$this->assertSame('SomeController@someAction', Integration::extractNameForLumenRoute($route, '/some-route'));
241+
242+
Integration::setControllersBaseNamespace(null);
243+
}
165244
}

0 commit comments

Comments
 (0)