├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── php-cs-fixer.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── .php-cs-fixer.dist.php ├── .phpunit.cache └── test-results ├── .phpunit.result.cache ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── missing-page-redirector.php └── src ├── Events ├── RedirectNotFound.php └── RouteWasHit.php ├── MissingPageRedirectorServiceProvider.php ├── MissingPageRouter.php ├── Redirector ├── ConfigurationRedirector.php └── Redirector.php └── RedirectsMissingPages.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://spatie.be/open-source/support-us 2 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: Check & fix styling 2 | 3 | on: [push] 4 | 5 | jobs: 6 | php-cs-fixer: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v2 12 | with: 13 | ref: ${{ github.head_ref }} 14 | 15 | - name: Run PHP CS Fixer 16 | uses: docker://oskarstark/php-cs-fixer-ga 17 | with: 18 | args: --config=.php-cs-fixer.dist.php --allow-risky=yes 19 | 20 | - name: Commit changes 21 | uses: stefanzweifel/git-auto-commit-action@v4 22 | with: 23 | commit_message: Fix styling 24 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | fail-fast: true 13 | matrix: 14 | os: [ubuntu-latest] 15 | php: [8.4, 8.3, 8.2, 8.1, 8.0] 16 | laravel: ['8.*', '9.*', '10.*', '11.*', '12.*'] 17 | dependency-version: [prefer-stable] 18 | include: 19 | - laravel: 10.* 20 | testbench: 8.* 21 | - laravel: 9.* 22 | testbench: 7.* 23 | - laravel: 8.* 24 | testbench: 6.* 25 | - laravel: 11.* 26 | testbench: 9.* 27 | - laravel: 12.* 28 | testbench: 10.* 29 | exclude: 30 | - laravel: 10.* 31 | php: 8.0 32 | - laravel: 5.8.* 33 | php: 8.0 34 | - laravel: 5.8.* 35 | php: 8.1 36 | - laravel: 6.* 37 | php: 8.1 38 | - laravel: 7.* 39 | php: 8.1 40 | - laravel: 9.* 41 | php: 7.4 42 | - laravel: 11.* 43 | php: 8.1 44 | - laravel: 11.* 45 | php: 8.0 46 | - laravel: 12.* 47 | php: 8.1 48 | - laravel: 12.* 49 | php: 8.0 50 | 51 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 52 | 53 | steps: 54 | - name: Checkout code 55 | uses: actions/checkout@v2 56 | 57 | - name: Setup PHP 58 | uses: shivammathur/setup-php@v2 59 | with: 60 | php-version: ${{ matrix.php }} 61 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 62 | coverage: none 63 | 64 | - name: Install dependencies 65 | run: | 66 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 67 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction 68 | 69 | - name: Execute tests 70 | run: vendor/bin/pest 71 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | with: 15 | ref: main 16 | 17 | - name: Update Changelog 18 | uses: stefanzweifel/changelog-updater-action@v1 19 | with: 20 | latest-version: ${{ github.event.release.name }} 21 | release-notes: ${{ github.event.release.body }} 22 | 23 | - name: Commit updated CHANGELOG 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | branch: main 27 | commit_message: Update CHANGELOG 28 | file_pattern: CHANGELOG.md 29 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->ignoreDotFiles(true) 10 | ->ignoreVCS(true); 11 | 12 | return (new PhpCsFixer\Config()) 13 | ->setRules([ 14 | '@PSR12' => true, 15 | 'array_syntax' => ['syntax' => 'short'], 16 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 17 | 'no_unused_imports' => true, 18 | 'not_operator_with_successor_space' => true, 19 | 'trailing_comma_in_multiline' => true, 20 | 'phpdoc_scalar' => true, 21 | 'unary_operator_spaces' => true, 22 | 'binary_operator_spaces' => true, 23 | 'blank_line_before_statement' => [ 24 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 25 | ], 26 | 'phpdoc_single_line_var_spacing' => true, 27 | 'phpdoc_var_without_name' => true, 28 | 'class_attributes_separation' => [ 29 | 'elements' => [ 30 | 'method' => 'one', 31 | ], 32 | ], 33 | 'method_argument_space' => [ 34 | 'on_multiline' => 'ensure_fully_multiline', 35 | 'keep_multiple_spaces_after_comma' => true, 36 | ], 37 | 'single_trait_insert_per_statement' => true, 38 | ]) 39 | ->setFinder($finder); 40 | -------------------------------------------------------------------------------- /.phpunit.cache/test-results: -------------------------------------------------------------------------------- 1 | {"version":"pest_2.36.0","defects":[],"times":{"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_not_interfere_with_existing_pages":0.037,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_redirect_a_non_existing_page_with_a_permanent_redirect":0.017,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_redirect_wildcard_routes":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_not_redirect_an_url_that_is_not_configured":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_can_use_named_properties":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_can_use_multiple_named_parameters_in_one_segment":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_can_optionally_set_the_redirect_status_code":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_can_use_optional_parameters#('\/old-segment', '\/new-segment')":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_can_use_optional_parameters#('\/old-segment\/old-segment2', '\/new-segment\/old-segment2')":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_can_use_optional_parameters#('\/old-segment\/old-segment2\/old-segment3', '\/new-segment\/old-segment2\/old-segment3')":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_by_default_it_will_not_redirect_requests_that_are_not_404s":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_fire_an_event_when_a_route_is_hit":0.002,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_redirect_depending_on_redirect_status_code_defined":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_not_redirect_if_the_status_code_is_not_specified_in_the_config_file":0.001,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_redirect_on_any_status_code":0.049,"P\\Tests\\RedirectsMissingPagesTest::__pest_evaluable_it_will_fire_an_event_when_no_redirect_was_found":0.001}} -------------------------------------------------------------------------------- /.phpunit.result.cache: -------------------------------------------------------------------------------- 1 | {"version":1,"defects":{"\/Users\/alexandrumanase\/Documents\/repos\/laravel-missing-page-redirector\/tests\/RedirectsMissingPagesTest.php::it":4},"times":{"\/Users\/alexandrumanase\/Documents\/repos\/laravel-missing-page-redirector\/tests\/RedirectsMissingPagesTest.php::it":0.002,"\/Users\/alexandrumanase\/Documents\/repos\/laravel-missing-page-redirector\/tests\/RedirectsMissingPagesTest.php::by":0.002}} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-missing-page-redirector` will be documented in this file 4 | 5 | ## 2.11.1 - 2025-02-21 6 | 7 | ### What's Changed 8 | 9 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-missing-page-redirector/pull/88 10 | 11 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.11.0...2.11.1 12 | 13 | ## 2.11.0 - 2025-01-06 14 | 15 | ### What's Changed 16 | 17 | * Update README.md by @hofmannsven in https://github.com/spatie/laravel-missing-page-redirector/pull/86 18 | * Update README.md by @chengkangzai in https://github.com/spatie/laravel-missing-page-redirector/pull/85 19 | * fix php8.4 nullable is deprecated by @it-can in https://github.com/spatie/laravel-missing-page-redirector/pull/87 20 | 21 | ### New Contributors 22 | 23 | * @hofmannsven made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/86 24 | * @chengkangzai made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/85 25 | * @it-can made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/87 26 | 27 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.10.0...2.11.0 28 | 29 | ## 2.10.0 - 2024-03-12 30 | 31 | ### What's Changed 32 | 33 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-missing-page-redirector/pull/84 34 | 35 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.9.4...2.10.0 36 | 37 | ## 2.9.4 - 2023-01-24 38 | 39 | ### What's Changed 40 | 41 | - Refactor tests to Pest by @alexmanase in https://github.com/spatie/laravel-missing-page-redirector/pull/79 42 | - Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/laravel-missing-page-redirector/pull/80 43 | - Laravel 10.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-missing-page-redirector/pull/81 44 | 45 | ### New Contributors 46 | 47 | - @alexmanase made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/79 48 | - @patinthehat made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/80 49 | 50 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.9.3...2.9.4 51 | 52 | ## 2.9.3 - 2022-10-13 53 | 54 | ### What's Changed 55 | 56 | - Use Laravel container on private Router - closes #77 by @rodrigopedra in https://github.com/spatie/laravel-missing-page-redirector/pull/78 57 | 58 | ### New Contributors 59 | 60 | - @rodrigopedra made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/78 61 | 62 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.9.2...2.9.3 63 | 64 | ## 2.9.2 - 2022-05-13 65 | 66 | ## What's Changed 67 | 68 | - remove Str::of for Laravel 6 compatibility by @chrisGeonet in https://github.com/spatie/laravel-missing-page-redirector/pull/76 69 | 70 | ## New Contributors 71 | 72 | - @chrisGeonet made their first contribution in https://github.com/spatie/laravel-missing-page-redirector/pull/76 73 | 74 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.9.1...2.9.2 75 | 76 | ## 2.9.1 - 2022-04-21 77 | 78 | - use `Str` class instead of `str` helper function 79 | 80 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.9.0...2.9.1 81 | 82 | ## 2.9.0 - 2022-04-21 83 | 84 | - Add support for wildcard route parameters that span multiple route segments (`/old/*` -> `/new/{wildcard}`) 85 | 86 | **Full Changelog**: https://github.com/spatie/laravel-missing-page-redirector/compare/2.8.0...2.9.0 87 | 88 | ## 2.7.2 - 2021-04-06 89 | 90 | - prep for Octane 91 | 92 | ## 2.7.1 - 2020-12-04 93 | 94 | - add support for PHP 8 95 | 96 | ## 2.7.0 - 2020-09-09 97 | 98 | - add support for Laravel 8 99 | 100 | ## 2.6.0 - 2020-03-03 101 | 102 | - add support for Laravel 7 103 | 104 | ## 2.5.0 - 2019-09-04 105 | 106 | - add support for Laravel 6 107 | 108 | ## 2.4.0 - 2019-02-27 109 | 110 | - drop support for PHP 7.1 and below 111 | - drop support for Laravel 5.7 and below 112 | 113 | ## 2.3.4 - 2019-02-27 114 | 115 | - add support for Laravel 5.8 116 | 117 | ## 2.3.3 - 2018-12-29 118 | 119 | - fix for PHP 7.3 120 | 121 | ## 2.3.2 - 2018-08-27 122 | 123 | - Added: Laravel 5.7 compatibility 124 | 125 | ## 2.3.1 - 2018-08-14 126 | 127 | - Fixed: Optional parameters not working as expected (#44) 128 | 129 | ## 2.3.0 - 2018-05-02 130 | 131 | - Added: an event will get fired when a route was not found 132 | 133 | ## 2.2.0 - 2018-02-08 134 | 135 | - Added: Laravel 5.6 compatibility 136 | 137 | ## 2.1.1 - 2017-10-19 138 | 139 | - Added: Response code to `RouteWasHit` event 140 | 141 | ## 2.1.0 - 2017-09-09 142 | 143 | - Added: Allow redirects to be enable on a status code basis 144 | 145 | ## 2.0.0 - 2017-08-31 146 | 147 | - Added: Laravel 5.5 compatibility 148 | - Removed: Dropped support for older Laravel versions 149 | - Changed: Renamed config file from `laravel-missing-page-redirector` to `missing-page-redirector` 150 | - Refactored tests 151 | 152 | ## 1.3.0 - 2017-06-11 153 | 154 | - Added: `RouteWasHit` event 155 | 156 | ## 1.2.0 - 2017-01-23 157 | 158 | - Added: Laravel 5.4 compatibility 159 | - Removed: Dropped support for older Laravel versions 160 | 161 | ## 1.1.0 - 2016-10-27 162 | 163 | - Added: Support for determining http status code for a redirect 164 | 165 | ## 1.0.0 - 2016-10-14 166 | 167 | - Initial release 168 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redirect missing pages in your Laravel application 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-missing-page-redirector.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-missing-page-redirector) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 5 | [![run-tests](https://github.com/spatie/laravel-missing-page-redirector/actions/workflows/run-tests.yml/badge.svg)](https://github.com/spatie/laravel-missing-page-redirector/actions/workflows/run-tests.yml) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-missing-page-redirector.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-missing-page-redirector) 7 | 8 | When transitioning from a old site to a new one your URLs may change. If your old site was popular you probably want to retain your SEO worth. One way of doing this is by providing [permanent redirects from your old URLs to your new URLs](https://support.google.com/webmasters/answer/93633?hl=en). This package makes that process very easy. 9 | 10 | When installed you only need to [add your redirects to the config file](https://github.com/spatie/laravel-missing-page-redirector#usage). Want to use the database as your source of redirects? [No problem](https://github.com/spatie/laravel-missing-page-redirector#creating-your-own-redirector)! 11 | 12 | ## Support us 13 | 14 | [](https://spatie.be/github-ad-click/laravel-missing-page-redirector) 15 | 16 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 17 | 18 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 19 | 20 | ## Installation 21 | 22 | You can install the package via composer: 23 | 24 | ``` bash 25 | composer require spatie/laravel-missing-page-redirector 26 | ``` 27 | 28 | The package will automatically register itself. 29 | 30 | Next, prepend/append the `Spatie\MissingPageRedirector\RedirectsMissingPages` middleware to your global middleware stack: 31 | 32 | ```php 33 | // bootstrap/app.php 34 | ->withMiddleware(function (Middleware $middleware) { 35 | $middleware->append([ 36 | \Spatie\MissingPageRedirector\RedirectsMissingPages::class, 37 | ]); 38 | }) 39 | ``` 40 | 41 | 42 | 43 | Finally you must publish the config file: 44 | 45 | ```php 46 | php artisan vendor:publish --provider="Spatie\MissingPageRedirector\MissingPageRedirectorServiceProvider" 47 | ``` 48 | 49 | This is the contents of the published config file: 50 | 51 | ```php 52 | return [ 53 | /* 54 | * This is the class responsible for providing the URLs which must be redirected. 55 | * The only requirement for the redirector is that it needs to implement the 56 | * `Spatie\MissingPageRedirector\Redirector\Redirector`-interface 57 | */ 58 | 'redirector' => \Spatie\MissingPageRedirector\Redirector\ConfigurationRedirector::class, 59 | 60 | /* 61 | * By default the package will only redirect 404s. If you want to redirect on other 62 | * response codes, just add them to the array. Leave the array empty to redirect 63 | * always no matter what the response code. 64 | */ 65 | 'redirect_status_codes' => [ 66 | \Symfony\Component\HttpFoundation\Response::HTTP_NOT_FOUND 67 | ], 68 | 69 | /* 70 | * When using the `ConfigurationRedirector` you can specify the redirects in this array. 71 | * You can use Laravel's route parameters here. 72 | */ 73 | 'redirects' => [ 74 | // '/non-existing-page' => '/existing-page', 75 | // '/old-blog/{url}' => '/new-blog/{url}', 76 | ], 77 | 78 | ]; 79 | ``` 80 | 81 | ## Usage 82 | 83 | Creating a redirect is easy. You just have to add an entry to the `redirects` key in the config file. 84 | 85 | ```php 86 | 'redirects' => [ 87 | '/non-existing-page' => '/existing-page', 88 | ], 89 | ``` 90 | 91 | You may use route parameters like you're used to when using Laravel's routes: 92 | 93 | ```php 94 | 'redirects' => [ 95 | '/old-blog/{url}' => '/new-blog/{url}', 96 | ], 97 | ``` 98 | 99 | Optional parameters are also... an option: 100 | 101 | ```php 102 | 'redirects' => [ 103 | '/old-blog/{url?}' => '/new-blog/{url}', 104 | ], 105 | ``` 106 | 107 | Finally, you can use an asterix (`*`) as a wildcard parameter that will match multiple URL segments (see [encoded URL slashes in the Laravel docs](https://laravel.com/docs/master/routing#parameters-encoded-forward-slashes) for more info). This is useful when you want to redirect a URL like `/old-blog/foo/bar/baz` to `/new-blog/foo/bar/baz`. 108 | 109 | ```php 110 | 'redirects' => [ 111 | '/old-blog/*' => '/new-blog/{wildcard}', // {wilcard} will be the entire path 112 | ], 113 | ``` 114 | 115 | By default the package only redirects if the request has a `404` response code but it's possible to be redirected on any response code. 116 | To achieve this you may change the ```redirect_status_codes``` option to an array of response codes or leave it empty if you wish to be redirected no matter what the response code was sent to the URL. 117 | You may override this using the following syntax to achieve this: 118 | 119 | ```php 120 | 'redirect_status_codes' => [\Symfony\Component\HttpFoundation\Response::HTTP_NOT_FOUND], 121 | ``` 122 | 123 | It is also possible to optionally specify which http response code is used when performing the redirect. By default the ```301 Moved Permanently``` response code is set. You may override this using the following syntax: 124 | 125 | ```php 126 | 'redirects' => [ 127 | 'old-page' => ['/new-page', 302], 128 | ], 129 | ``` 130 | 131 | ## Events 132 | 133 | The package will fire a `RouteWasHit` event when it found a redirect for the route. A `RedirectNotFound` is fired when no redirect was found. 134 | 135 | ## Creating your own redirector 136 | 137 | By default this package will use the `Spatie\MissingPageRedirector\Redirector\ConfigurationRedirector` which will get its redirects from the config file. If you want to use another source for your redirects (for example a database) you can create your own redirector. 138 | 139 | A valid redirector is any class that implements the `Spatie\MissingPageRedirector\Redirector\Redirector`-interface. That interface looks like this: 140 | 141 | ```php 142 | namespace Spatie\MissingPageRedirector\Redirector; 143 | 144 | use Symfony\Component\HttpFoundation\Request; 145 | 146 | interface Redirector 147 | { 148 | public function getRedirectsFor(Request $request): array; 149 | } 150 | 151 | ``` 152 | 153 | The `getRedirectsFor` method should return an array in which the keys are the old URLs and the values the new URLs. 154 | 155 | ## If you want to use `Route::fallback` 156 | 157 | If you do not wish to overwrite the default redirector, or if you already have existing `Route::fallback` logic based on [laravel docs](https://laravel.com/docs/11.x/routing#fallback-routes), you can use this package as follow. 158 | In the bottom of your `web.php` file, 159 | 160 | ```php 161 | use Spatie\MissingPageRedirector\MissingPageRouter; 162 | //... Your other route 163 | 164 | Route::fallback(function (Request $request) { 165 | $redirectResponse = app(MissingPageRouter::class)->getRedirectFor($request); 166 | 167 | if ($redirectResponse !== null) { 168 | return $redirectResponse; 169 | } 170 | //... Your other logic 171 | }); 172 | ``` 173 | You can adjust the priority of redirect base on your needs. 174 | 175 | ## Changelog 176 | 177 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 178 | 179 | ## Testing 180 | 181 | ``` bash 182 | $ composer test 183 | ``` 184 | 185 | ## Contributing 186 | 187 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 188 | 189 | ## Security 190 | 191 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 192 | 193 | ## Credits 194 | 195 | - [Freek Van der Herten](https://github.com/freekmurze) 196 | - [All Contributors](../../contributors) 197 | 198 | ## License 199 | 200 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 201 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-missing-page-redirector", 3 | "description": "Redirect missing pages in your Laravel application", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-missing-page-redirector" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-missing-page-redirector", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0|^8.1", 20 | "laravel/framework": "^8.28|^9.0|^10.0|^11.0|^12.0", 21 | "spatie/url": "^1.0|^2.0" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", 25 | "pestphp/pest": "^1.22|^2.34|^3.7", 26 | "phpunit/phpunit": "^9.0|^9.3|^10.5|^11.5.3" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Spatie\\MissingPageRedirector\\": "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Spatie\\MissingPageRedirector\\Test\\": "tests" 36 | } 37 | }, 38 | "scripts": { 39 | "test": "vendor/bin/pest" 40 | }, 41 | "config": { 42 | "sort-packages": true, 43 | "allow-plugins": { 44 | "pestphp/pest-plugin": true 45 | } 46 | }, 47 | "extra": { 48 | "laravel": { 49 | "providers": [ 50 | "Spatie\\MissingPageRedirector\\MissingPageRedirectorServiceProvider" 51 | ] 52 | } 53 | }, 54 | "minimum-stability": "dev", 55 | "prefer-stable": true 56 | } 57 | -------------------------------------------------------------------------------- /config/missing-page-redirector.php: -------------------------------------------------------------------------------- 1 | \Spatie\MissingPageRedirector\Redirector\ConfigurationRedirector::class, 10 | 11 | /* 12 | * By default the package will only redirect 404s. If you want to redirect on other 13 | * response codes, just add them to the array. Leave the array empty to redirect 14 | * always no matter what the response code. 15 | */ 16 | 'redirect_status_codes' => [ 17 | \Symfony\Component\HttpFoundation\Response::HTTP_NOT_FOUND, 18 | ], 19 | 20 | /* 21 | * When using the `ConfigurationRedirector` you can specify the redirects in this array. 22 | * You can use Laravel's route parameters here. 23 | */ 24 | 'redirects' => [ 25 | // '/non-existing-page' => '/existing-page', 26 | // '/old-blog/{url}' => '/new-blog/{url}', 27 | ], 28 | 29 | ]; 30 | -------------------------------------------------------------------------------- /src/Events/RedirectNotFound.php: -------------------------------------------------------------------------------- 1 | request = $request; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Events/RouteWasHit.php: -------------------------------------------------------------------------------- 1 | route = $route; 19 | 20 | $this->missingUrl = $missingUrl; 21 | 22 | $this->statusCode = $statusCode; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MissingPageRedirectorServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 14 | __DIR__.'/../config/missing-page-redirector.php' => config_path('missing-page-redirector.php'), 15 | ], 'config'); 16 | 17 | $this->app->bind(Redirector::class, config('missing-page-redirector.redirector')); 18 | 19 | $this->app->bind(MissingPageRouter::class, function ($app) { 20 | $router = new Router($app['events'], $app); 21 | 22 | $redirector = $app->make(Redirector::class); 23 | 24 | return new MissingPageRouter($router, $redirector); 25 | }); 26 | } 27 | 28 | public function register() 29 | { 30 | $this->mergeConfigFrom(__DIR__.'/../config/missing-page-redirector.php', 'missing-page-redirector'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MissingPageRouter.php: -------------------------------------------------------------------------------- 1 | router = $router; 25 | $this->redirector = $redirector; 26 | } 27 | 28 | /** 29 | * @param \Symfony\Component\HttpFoundation\Request $request 30 | * 31 | * @return \Illuminate\Http\Response|null 32 | */ 33 | public function getRedirectFor(Request $request) 34 | { 35 | $redirects = $this->redirector->getRedirectsFor($request); 36 | 37 | collect($redirects)->each(function ($redirects, $missingUrl) { 38 | if (Str::contains($missingUrl, '*')) { 39 | $missingUrl = str_replace('*', '{wildcard}', $missingUrl); 40 | } 41 | 42 | $this->router->get($missingUrl, function () use ($redirects, $missingUrl) { 43 | $redirectUrl = $this->determineRedirectUrl($redirects); 44 | $statusCode = $this->determineRedirectStatusCode($redirects); 45 | 46 | event(new RouteWasHit($redirectUrl, $missingUrl, $statusCode)); 47 | 48 | return redirect()->to( 49 | $redirectUrl, 50 | $statusCode 51 | ); 52 | })->where('wildcard', '.*'); 53 | }); 54 | 55 | try { 56 | return $this->router->dispatch($request); 57 | } catch (Exception $e) { 58 | event(new RedirectNotFound($request)); 59 | 60 | return; 61 | } 62 | } 63 | 64 | protected function determineRedirectUrl($redirects): string 65 | { 66 | if (is_array($redirects)) { 67 | return $this->resolveRouterParameters($redirects[0]); 68 | } 69 | 70 | return $this->resolveRouterParameters($redirects); 71 | } 72 | 73 | protected function determineRedirectStatusCode($redirects): int 74 | { 75 | return is_array($redirects) ? $redirects[1] : Response::HTTP_MOVED_PERMANENTLY; 76 | } 77 | 78 | protected function resolveRouterParameters(string $redirectUrl): string 79 | { 80 | foreach ($this->router->getCurrentRoute()->parameters() as $key => $value) { 81 | $redirectUrl = str_replace("{{$key}}", $value, $redirectUrl); 82 | } 83 | 84 | $redirectUrl = preg_replace('/\/{[\w-]+}/', '', $redirectUrl); 85 | 86 | return $redirectUrl; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Redirector/ConfigurationRedirector.php: -------------------------------------------------------------------------------- 1 | shouldRedirect($response)) { 15 | return $response; 16 | } 17 | 18 | $redirectResponse = app(MissingPageRouter::class)->getRedirectFor($request); 19 | 20 | return $redirectResponse ?? $response; 21 | } 22 | 23 | protected function shouldRedirect($response): bool 24 | { 25 | $redirectStatusCodes = config('missing-page-redirector.redirect_status_codes'); 26 | 27 | if (is_null($redirectStatusCodes)) { 28 | return false; 29 | } 30 | 31 | if (! count($redirectStatusCodes)) { 32 | return true; 33 | } 34 | 35 | return in_array($response->getStatusCode(), $redirectStatusCodes); 36 | } 37 | } 38 | --------------------------------------------------------------------------------