Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
markvaneijk committed Apr 20, 2024
1 parent 30e9450 commit 0848b10
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 9 deletions.
Empty file removed resources/views/.gitkeep
Empty file.
45 changes: 45 additions & 0 deletions src/Middleware/EnsureTrailingSlash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Vormkracht10\TrailingSlash\Middleware;

use Closure;

class EnsureTrailingSlash
{
public function handle($request, Closure $next)
{
if (! in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS'])) {
return $next($request);
}

// parse our own constructed url, Laravel is known for letting the trailing slash out in some cases
$url = parse_url($request->getScheme().'://'.$request->getHost().$request->server('REQUEST_URI'));

$url['path'] ??= '/';

// don't mess with urls already ending on trailing slash
if (str_ends_with($url['path'], '/')) {
return $next($request);
}

// when a file is requested (but probably 404 as it came here), don't mess with trailing slash
if (str_contains($url['path'], '.')) {
return $next($request);
}

// ajax or pjax shouldn't need redirection
if ($request->server('HTTP_X_REQUESTED_WITH')) {
return $next($request);
}

return $this->redirect($url['path'].'/');
}

protected function redirect(string $url)
{
/* @var $redirector \Illuminate\Routing\Redirector */
$redirector = app('redirect');

return $redirector->to($url, 301);
}
}
47 changes: 47 additions & 0 deletions src/Middleware/EnsureWithoutTrailingSlash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Vormkracht10\TrailingSlash\Middleware;

use Closure;

class EnsureWithoutTrailingSlash
{
public function handle($request, Closure $next)
{
if (! in_array($request->getMethod(), ['GET', 'HEAD', 'OPTIONS'])) {
return $next($request);
}

// parse our own constructed url, Laravel is known for letting the trailing slash out in some cases
$url = parse_url($request->getScheme().'://'.$request->getHost().$request->server('REQUEST_URI'));

$url['path'] ??= '/';

// don't mess with urls already ending on trailing slash
if (str_ends_with($url['path'], '/')) {
return $next($request);
}

// when a file is requested (but probably 404 as it came here), don't mess with trailing slash
if (str_contains($url['path'], '.')) {
return $next($request);
}

// ajax or pjax shouldn't need redirection
if ($request->server('HTTP_X_REQUESTED_WITH')) {
return $next($request);
}

$url['path'] = rtrim($url['path'], '/');

return $this->redirect($request->getScheme().'://'.$request->getHost().$url['path'].$url['query']);
}

protected function redirect(string $url)
{
/* @var $redirector \Illuminate\Routing\Redirector */
$redirector = app('redirect');

return $redirector->to($url, 301);
}
}
56 changes: 56 additions & 0 deletions src/RoutingServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Vormkracht10\TrailingSlash;

use Illuminate\Routing\RoutingServiceProvider as BaseRoutingServiceProvider;

class RoutingServiceProvider extends BaseRoutingServiceProvider
{
public function register(): void
{
$this->registerUrlGenerator();
}

protected function registerUrlGenerator(): void
{
$this->app->singleton('url', function ($app) {
$routes = $app['router']->getRoutes();

// The URL generator needs the route collection that exists on the router.
// Keep in mind this is an object, so we're passing by references here
// and all the registered routes will be available to the generator.
$app->instance('routes', $routes);

$url = new UrlGenerator(
$routes,
$app->rebinding(
'request',
function ($app, $request) {
$app['url']->setRequest($request);
}
),
$app['config']['app.asset_url']
);

// Next we will set a few service resolvers on the URL generator, so it can
// get the information it needs to function. This just provides some of
// the convenience features to this URL generator like "signed" URLs.
$url->setSessionResolver(function () use ($app) {
return $app['session'] ?? null;
});

$url->setKeyResolver(function () use ($app) {
return $app->make('config')->get('app.key');
});

// If the route collection is "rebound", for example, when the routes stay
// cached for the application, we will need to rebind the routes on the
// URL generator instance so it has the latest version of the routes.
$app->rebinding('routes', function ($app, $routes) {
$app['url']->setRoutes($routes);
});

return $url;
});
}
}
50 changes: 49 additions & 1 deletion src/TrailingSlashServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,55 @@ class TrailingSlashServiceProvider extends PackageServiceProvider
public function configurePackage(Package $package): void
{
$package
->name('laravel-trailing-slash')
->name('trailing-slash')
->hasConfigFile();
}

public function packageBooted()
{
if($this->app->config->get('trailing-slash.enabled')) {
$this->registerUrlGeneratorWithTrailingSlash();
}
}

protected function registerUrlGeneratorWithTrailingSlash(): void
{
$this->app->singleton('url', function ($app) {
$routes = $app['router']->getRoutes();

// The URL generator needs the route collection that exists on the router.
// Keep in mind this is an object, so we're passing by references here
// and all the registered routes will be available to the generator.
$app->instance('routes', $routes);

$url = new UrlGenerator(
$routes,
$app->rebinding(
'request',
$this->requestRebinder()
),
$app['config']['app.asset_url']
);

// Next we will set a few service resolvers on the URL generator, so it can
// get the information it needs to function. This just provides some of
// the convenience features to this URL generator like "signed" URLs.
$url->setSessionResolver(function () use ($app) {
return $app['session'] ?? null;
});

$url->setKeyResolver(function () use ($app) {
return $app->make('config')->get('app.key');
});

// If the route collection is "rebound", for example, when the routes stay
// cached for the application, we will need to rebind the routes on the
// URL generator instance so it has the latest version of the routes.
$app->rebinding('routes', function ($app, $routes) {
$app['url']->setRoutes($routes);
});

return $url;
});
}
}
23 changes: 23 additions & 0 deletions src/UrlGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Vormkracht10\TrailingSlash;

use Illuminate\Routing\UrlGenerator as BaseUrlGenerator;
use Illuminate\Support\Str;

class UrlGenerator extends BaseUrlGenerator
{
/**
* Format the given URL segments into a single URL.
*
* @param string $root
* @param string $path
* @param \Illuminate\Routing\Route|null $route
*/
public function format($root, $path, $route = null): string
{
$trailingSlash = (Str::contains($path, '#') ? '' : '/');

return rtrim(parent::format($root, $path, $route), '/').$trailingSlash;
}
}
6 changes: 3 additions & 3 deletions tests/ArchTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php

arch('it will not use debugging functions')
->expect(['dd', 'dump', 'ray'])
->each->not->toBeUsed();
//arch('it will not use debugging functions')
// ->expect(['dd', 'dump'])
// ->each->not->toBeUsed();
5 changes: 0 additions & 5 deletions tests/ExampleTest.php

This file was deleted.

46 changes: 46 additions & 0 deletions tests/MiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

use Illuminate\Http\Response;
use Illuminate\Support\Facades\Request;
use Vormkracht10\TrailingSlash\Middleware\EnsureWithoutTrailingSlash;

it('can access root url', function () {
Route::get('/', fn() => 'Hello World');
Route::get('route-without-trailing-slash', fn() => 'Hello World');

$this->get('/')->assertOk();
});

it('ensure url is with trailing slash', function () {
$request = Request::create('/without-trailing-slash');

$next = function () {
return response('This is a secret place');
};

$middleware = new \Vormkracht10\TrailingSlash\Middleware\EnsureTrailingSlash();
$response = $middleware->handle($request, $next);

$this->assertEquals(Response::HTTP_MOVED_PERMANENTLY, $response->getStatusCode());

$request = Request::create('/with-trailing-slash/');

$next = function () {
return response('This is a secret place');
};

$middleware = new \Vormkracht10\TrailingSlash\Middleware\EnsureTrailingSlash();
$response = $middleware->handle($request, $next);

$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
});


it('ensure url is without trailing slash', function () {
Route::get('/with-trailing-slash', fn() => '');

$this->withMiddleware(
EnsureWithoutTrailingSlash::class,
)->get('/with-trailing-slash/')
->assertMovedPermanently();
});

0 comments on commit 0848b10

Please sign in to comment.