├── .env.example ├── .scrutinizer.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── url-rewrite.php ├── database └── migrations │ └── create_url_rewrites_table.php.stub ├── nova └── UrlRewrite.php.stub ├── resources └── lang │ ├── en │ └── translations.php │ └── nl │ └── translations.php └── src ├── Entities └── UrlRewrite.php ├── Exceptions ├── UrlRewriteAlreadyExistsException.php └── UrlRewriteRegenerationFailed.php ├── Facades └── UrlRewrite.php ├── Http └── UrlRewriteController.php ├── Nova ├── Actions │ └── Regenerate.php └── Filters │ ├── RedirectTypeFilter.php │ └── TypeFilter.php ├── Repositories ├── Decorators │ └── CachingUrlRewriteRepository.php ├── Interfaces │ └── UrlRewriteInterface.php └── UrlRewriteRepository.php ├── ServiceProvider.php └── Traits └── HasUrlRewrite.php /.env.example: -------------------------------------------------------------------------------- 1 | DB_USERNAME=username 2 | DB_PASSWORD=secret 3 | DB_DATABASE=laravel_url_rewrites -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | 4 | checks: 5 | php: 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-url-rewrites` will be documented in this file. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Ruthger Idema 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easily add URL rewrites to a Laravel app 2 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ruthgeridema/laravel-url-rewrites.svg?style=flat-square)](https://packagist.org/packages/ruthgeridema/laravel-url-rewrites) 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 4 | [![Build Status](https://travis-ci.org/ruthgeridema/laravel-url-rewrites.svg?branch=master)](https://travis-ci.org/ruthgeridema/laravel-url-rewrites) 5 | [![Quality Score](https://img.shields.io/scrutinizer/g/ruthgeridema/laravel-url-rewrites.svg?style=flat-square)](https://scrutinizer-ci.com/g/ruthgeridema/laravel-url-rewrites) 6 | [![StyleCI](https://styleci.io/repos/174381685/shield?branch=master)](https://styleci.io/repos/174381685) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/ruthgeridema/laravel-url-rewrites.svg?style=flat-square)](https://packagist.org/packages/ruthgeridema/laravel-url-rewrites) 8 | 9 | Very easy to use URL rewrite package. Follow the instructions and you're good to go! 10 | 11 | You can find an example project on my Github: [view example project](https://github.com/ruthgeridema/laravel-url-rewrites-example) 12 | This example project features the following: 13 | - Eloquent observers to add URL rewrites automatically 14 | - Usage of the trait 15 | - Some use cases 16 | 17 | ## Requirements 18 | 19 | This package requires Laravel 5.8 or higher, PHP 7.2 or higher and a database that supports json fields and functions such as MySQL 5.7 or higher. 20 | 21 | ## Installation 22 | 23 | You can install the package via composer: 24 | 25 | ``` bash 26 | composer require ruthgeridema/laravel-url-rewrites 27 | ``` 28 | 29 | The package will automatically register itself. 30 | 31 | Register the routes the feeds will be displayed on using the `rewrites`-macro. 32 | You need to place it at the bottom of your routes file. 33 | 34 | ```php 35 | // In routes/web.php 36 | Route::rewrites(); 37 | ``` 38 | 39 | You can publish the migration with: 40 | 41 | ```bash 42 | php artisan vendor:publish --provider="RuthgerIdema\UrlRewrite\ServiceProvider" --tag="migrations" 43 | ``` 44 | 45 | After the migration has been published you can create the `url_rewrites` table by running the migration: 46 | 47 | ```bash 48 | php artisan migrate 49 | ``` 50 | 51 | You can optionally publish the config file with: 52 | 53 | ```bash 54 | php artisan vendor:publish --provider="RuthgerIdema\UrlRewrite\ServiceProvider" --tag="config" 55 | ``` 56 | 57 | This is the contents of the published config file: 58 | 59 | ```php 60 | 'url_rewrites', 64 | 'repository' => \RuthgerIdema\UrlRewrite\Repositories\UrlRewriteRepository::class, 65 | 'model' => \RuthgerIdema\UrlRewrite\Entities\UrlRewrite::class, 66 | 'cache' => true, 67 | 'cache-decorator' => \RuthgerIdema\UrlRewrite\Repositories\Decorators\CachingUrlRewriteRepository::class, 68 | 'types' => [ 69 | 'product' => [ 70 | 'route' => 'product', 71 | 'attributes' => ['id'], 72 | ], 73 | 'category' => [ 74 | 'route' => 'category', 75 | 'attributes' => ['id'], 76 | ] 77 | ], 78 | ]; 79 | ``` 80 | #### Laravel Nova 81 | Using Laravel Nova? You can publish the Nova class to App/Nova with the following command 82 | 83 | ```bash 84 | php artisan vendor:publish --provider="RuthgerIdema\UrlRewrite\ServiceProvider" --tag="nova" 85 | ``` 86 | 87 | In the near future I will publish a Laravel Nova package with features like reindexing the URL rewrites. 88 | ## Usage 89 | 90 | ### Forward request 91 | 92 | Let's say you've got a controller route 'product/{id}' and you have a product 'Apple Airpods' with id=5. 93 | When you visit 'apple-airpods' this package will forward the request to the controller but keeps the clean url. 94 | 95 | The following code adds this to the database: 96 | ```php 97 | UrlRewrite::create('apple-airpods', 'product/5') 98 | ``` 99 | 100 | ### Use named routes 101 | You must specify the types in the config. 102 | ```php 103 | UrlRewrite::create('apple-airpods', null, 'product', ["id" => 5]) 104 | ``` 105 | 106 | To regenerate the target path you can use 107 | ```php 108 | UrlRewrite::regenerateRoute($urlRewrite) 109 | UrlRewrite::regenerateAll() 110 | UrlRewrite::regenerateRoutesFromType($type) 111 | ``` 112 | 113 | To automatically add the URL attribute to an Eloquent model, you have to add the HasUrlRewrite trait to an Eloquent model. 114 | You also need to add the urlRewriteType and optionally add 'url' to the appends array. 115 | 116 | ```php 117 | use HasUrlRewrite; 118 | public $urlRewriteType = 'category'; 119 | protected $appends = ['url']; 120 | ``` 121 | 122 | Once this is done you can simply call `Model::find(1)->url` to get the url of the model. 123 | 124 | ### Redirect 125 | 126 | 301 redirect 127 | ```php 128 | UrlRewrite::create('apple-airpods', 'product/5', null, null, 1) 129 | ``` 130 | 302 redirect 131 | ```php 132 | UrlRewrite::create('apple-airpods', 'product/5', null, null, 2) 133 | ``` 134 | 135 | ### Other functions 136 | ```php 137 | UrlRewrite::all() 138 | UrlRewrite::find($id) 139 | UrlRewrite::delete($id) 140 | UrlRewrite::update($data, $id) 141 | UrlRewrite::getByRequestPath('apple-airpods') 142 | UrlRewrite::getByTargetPath('product/5') 143 | UrlRewrite::getByTypeAndAttributes('product', ["id" => 5]) 144 | ``` 145 | 146 | ## Testing 147 | 148 | 1. Copy `.env.example` to `.env` and fill in your database credentials. 149 | 2. Run `composer test`. 150 | 151 | ### Changelog 152 | 153 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 154 | 155 | ## Contributing 156 | 157 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 158 | 159 | ## Security 160 | 161 | If you discover any security related issues, please email ruthger.idema@gmail.com instead of using the issue tracker. 162 | 163 | 164 | ## Credits 165 | 166 | - [Ruthger Idema](https://github.com/ruthgeridema) 167 | - [All Contributors](../../contributors) 168 | 169 | Special thanks for Spatie for their guidelines and their packages as an inspiration 170 | - [Spatie](https://spatie.be) 171 | 172 | ## License 173 | 174 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 175 | 176 | 177 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ruthgeridema/laravel-url-rewrites", 3 | "description": "Easy URL rewrites in your Laravel application", 4 | "keywords": [ 5 | "url", 6 | "rewrite", 7 | "laravel" 8 | ], 9 | "homepage": "https://github.com/ruthgeridema/laravel-url-rewrites", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Ruthger Idema", 14 | "email": "ruthger.idema@gmail.com", 15 | "homepage": "https://ide.ma" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.2", 20 | "laravel/framework": "*" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^8.0", 24 | "orchestra/testbench": "~3.8.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "RuthgerIdema\\UrlRewrite\\": "src" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "RuthgerIdema\\UrlRewrite\\Test\\": "tests" 34 | } 35 | }, 36 | "scripts": { 37 | "test": "vendor/bin/phpunit" 38 | }, 39 | "config": { 40 | "sort-packages": true 41 | }, 42 | "extra": { 43 | "laravel": { 44 | "providers": [ 45 | "RuthgerIdema\\UrlRewrite\\ServiceProvider" 46 | ] 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /config/url-rewrite.php: -------------------------------------------------------------------------------- 1 | 'url_rewrites', 5 | 'repository' => \RuthgerIdema\UrlRewrite\Repositories\UrlRewriteRepository::class, 6 | 'model' => \RuthgerIdema\UrlRewrite\Entities\UrlRewrite::class, 7 | 'cache' => false, 8 | 'cache-tag' => 'url_rewrites', 9 | 'cache-ttl' => 86400, 10 | 'cache-decorator' => \RuthgerIdema\UrlRewrite\Repositories\Decorators\CachingUrlRewriteRepository::class, 11 | 'types' => [ 12 | 'product' => [ 13 | 'route' => 'product', 14 | 'attributes' => ['id'], 15 | ], 16 | 'category' => [ 17 | 'route' => 'category', 18 | 'attributes' => ['id'], 19 | ], 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /database/migrations/create_url_rewrites_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 13 | $table->string('type')->nullable(); 14 | $table->json('type_attributes')->nullable(); 15 | $table->string('request_path')->index(); 16 | $table->string('target_path'); 17 | $table->smallInteger('redirect_type')->default(0); 18 | $table->text('description')->nullable(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('url_rewrites'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /nova/UrlRewrite.php.stub: -------------------------------------------------------------------------------- 1 | sortable(), 37 | 38 | Text::make(trans('urlrewrites::translations.request_path'), 'request_path')->sortable()->rules('required'), 39 | 40 | Text::make(trans('urlrewrites::translations.target_path'), 'target_path')->sortable(), 41 | 42 | Select::make(trans('urlrewrites::translations.redirect_type'), 'redirect_type')->options( 43 | \RuthgerIdema\UrlRewrite\Entities\UrlRewrite::getRedirectTypeOptionsArray() 44 | )->displayUsingLabels()->sortable()->rules('required'), 45 | 46 | Select::make(trans('urlrewrites::translations.type'), 'type')->options( 47 | \RuthgerIdema\UrlRewrite\Entities\UrlRewrite::getPossibleTypesArray() 48 | )->displayUsingLabels(), 49 | 50 | /** Idea: replace with dynamic fields based on `type` */ 51 | Code::make(trans('urlrewrites::translations.type_attributes'), 'type_attributes')->json(), 52 | 53 | Text::make(trans('urlrewrites::translations.request_path'), 'description')->hideFromIndex(), 54 | 55 | DateTime::make(trans('urlrewrites::translations.created_at'), 'created_at')->onlyOnDetail(), 56 | 57 | DateTime::make(trans('urlrewrites::translations.updated_at'), 'updated_at')->onlyOnDetail() 58 | ]; 59 | } 60 | 61 | public function filters(Request $request): array 62 | { 63 | return [ 64 | new TypeFilter(), 65 | new RedirectTypeFilter() 66 | ]; 67 | } 68 | 69 | public function actions(Request $request): array 70 | { 71 | return [ 72 | new Regenerate() 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /resources/lang/en/translations.php: -------------------------------------------------------------------------------- 1 | 'URL rewrites', 5 | 'forward' => 'No redirect', 6 | 'permanent' => 'Permanent (301)', 7 | 'temporary' => 'Temporary (302)', 8 | 'type' => 'Type', 9 | 'type_attributes' => 'Type attributes', 10 | 'target_path' => 'Target path', 11 | 'request_path' => 'Request path', 12 | 'redirect_type' => 'Redirect type', 13 | 'description' => 'Description', 14 | 'created_at' => 'Created at', 15 | 'updated_at' => 'Updated at', 16 | 'regenerated' => 'Regenerated!', 17 | ]; 18 | -------------------------------------------------------------------------------- /resources/lang/nl/translations.php: -------------------------------------------------------------------------------- 1 | 'URL herschrijven', 5 | 'forward' => 'Geen omleiding', 6 | 'permanent' => 'Permanent (301)', 7 | 'temporary' => 'Tijdelijk (302)', 8 | 'type' => 'Type', 9 | 'type_attributes' => 'Type attributen', 10 | 'target_path' => 'Doelpad', 11 | 'request_path' => 'Gevraagd pad', 12 | 'redirect_type' => 'Omleiding type', 13 | 'description' => 'Omschrijving', 14 | 'created_at' => 'Aangemaakt op', 15 | 'updated_at' => 'Bijgewerkt op', 16 | 'regenerated' => 'Geregenereerd!', 17 | ]; 18 | -------------------------------------------------------------------------------- /src/Entities/UrlRewrite.php: -------------------------------------------------------------------------------- 1 | 'array', 33 | ]; 34 | 35 | public function __construct(?array $attributes = []) 36 | { 37 | if (! isset($this->table)) { 38 | $this->setTable(config('url-rewrite.table-name')); 39 | } 40 | 41 | parent::__construct($attributes); 42 | } 43 | 44 | public function isForward(): bool 45 | { 46 | return $this->redirect_type === static::FORWARD; 47 | } 48 | 49 | public function isRedirect(): bool 50 | { 51 | return $this->redirect_type !== static::FORWARD; 52 | } 53 | 54 | public function getRedirectType(): int 55 | { 56 | return $this->redirect_type === static::PERMANENT ? 301 : 302; 57 | } 58 | 59 | public function getByTypeAndAttributes(string $type, array $attributes) 60 | { 61 | $query = $this->where('type', $type); 62 | 63 | foreach ($attributes as $key => $attribute) { 64 | $query = $query->where("type_attributes->$key", (string) $attribute); 65 | } 66 | 67 | return $query; 68 | } 69 | 70 | public static function getRedirectTypeOptionsArray(): array 71 | { 72 | return [ 73 | static::FORWARD => trans('urlrewrites::translations.forward'), 74 | static::PERMANENT => trans('urlrewrites::translations.permanent'), 75 | static::TEMPORARY => trans('urlrewrites::translations.temporary'), 76 | ]; 77 | } 78 | 79 | public static function getPossibleTypesArray(): array 80 | { 81 | $array = []; 82 | 83 | foreach (array_keys(config('url-rewrite.types')) as $type) { 84 | $array[$type] = $type; 85 | } 86 | 87 | return $array; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Exceptions/UrlRewriteAlreadyExistsException.php: -------------------------------------------------------------------------------- 1 | id}` has no `{$column}`"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Facades/UrlRewrite.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 21 | } 22 | 23 | public function __invoke($url): object 24 | { 25 | if (! $urlRewrite = $this->repository->getByRequestPath($url)) { 26 | abort(404); 27 | } 28 | 29 | if ($urlRewrite->isForward()) { 30 | return $this->forwardResponse($urlRewrite->target_path); 31 | } 32 | 33 | return redirect($urlRewrite->target_path, $urlRewrite->getRedirectType()); 34 | } 35 | 36 | protected function forwardResponse($url): Response 37 | { 38 | return Route::dispatch( 39 | Request::create( 40 | '/'.ltrim($url, '/'), 41 | request()->getMethod() 42 | ) 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Nova/Actions/Regenerate.php: -------------------------------------------------------------------------------- 1 | id failed: $exception->getMessage()"); 28 | } 29 | } 30 | 31 | return Action::message($i.' '.trans('urlrewrites::translations.regenerated')); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Nova/Filters/RedirectTypeFilter.php: -------------------------------------------------------------------------------- 1 | where('redirect_type', $value); 23 | } 24 | 25 | public function options(Request $request): array 26 | { 27 | return UrlRewrite::getRedirectTypeOptionsArray(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Nova/Filters/TypeFilter.php: -------------------------------------------------------------------------------- 1 | where('type', $value); 23 | } 24 | 25 | public function options(Request $request): array 26 | { 27 | return array_flip(UrlRewrite::getPossibleTypesArray()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Repositories/Decorators/CachingUrlRewriteRepository.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 39 | $this->cache = $cache; 40 | $this->addTagIfPossible(); 41 | } 42 | 43 | protected function remember(string $key, string $method, ...$arguments) 44 | { 45 | return $this->cache->remember( 46 | $key, 47 | $this->getTtl(), 48 | function () use ($method, $arguments) { 49 | return $this->repository->{$method}(...$arguments); 50 | } 51 | ); 52 | } 53 | 54 | protected function addTagIfPossible(): void 55 | { 56 | if ($this->cache->getStore() instanceof TaggableStore) { 57 | $this->cache = $this->cache->tags(config('url-rewrite.cache-key')); 58 | } 59 | } 60 | 61 | protected function getTtl(): int 62 | { 63 | return config('url-rewrite.cache-ttl'); 64 | } 65 | 66 | public function find(int $id): ?object 67 | { 68 | return $this->remember(self::URL_REWRITE_ID.$id, __FUNCTION__, $id); 69 | } 70 | 71 | public function getByRequestPath(string $url): ?object 72 | { 73 | return $this->remember(static::URL_REWRITE_REQUEST_PATH.md5($url), __FUNCTION__, $url); 74 | } 75 | 76 | public function all(): ?object 77 | { 78 | return $this->remember(static::URL_REWRITE_ALL, __FUNCTION__); 79 | } 80 | 81 | public function getByTargetPath(string $url): ?object 82 | { 83 | return $this->remember(static::URL_REWRITE_TARGET_PATH.md5($url), __FUNCTION__, $url); 84 | } 85 | 86 | public function getByTypeAndAttributes(string $type, array $attributes): ?object 87 | { 88 | return $this->remember( 89 | self::URL_REWRITE_TYPE_ATTRIBUTES.md5($type.json_encode($attributes)), 90 | __FUNCTION__, 91 | $type, 92 | $attributes 93 | ); 94 | } 95 | 96 | public function getModel(): object 97 | { 98 | return $this->repository->getModel(); 99 | } 100 | 101 | public function setModel(object $model): object 102 | { 103 | return $this->repository->setModel($model); 104 | } 105 | 106 | public function delete(int $id): bool 107 | { 108 | $deleted = $this->repository->delete($id); 109 | 110 | $this->forgetById($id); 111 | 112 | return $deleted; 113 | } 114 | 115 | protected function forgetById(int $id): void 116 | { 117 | if ($model = $this->find($id)) { 118 | $this->cache->forget(static::URL_REWRITE_ALL); 119 | $this->cache->forget(static::URL_REWRITE_ID.$model->id); 120 | $this->cache->forget(static::URL_REWRITE_REQUEST_PATH.md5($model->request_path)); 121 | $this->cache->forget(static::URL_REWRITE_TARGET_PATH.md5($model->target_path)); 122 | $this->cache->forget(static::URL_REWRITE_TYPE_ATTRIBUTES.md5($model->type.json_encode($model->type_attributes))); 123 | } 124 | } 125 | 126 | public function create( 127 | string $requestPath, 128 | ?string $targetPath, 129 | ?string $type = null, 130 | ?array $typeAttributes = null, 131 | int $redirectType = 0, 132 | ?string $description = null, 133 | ?bool $unique = false 134 | ): object { 135 | return $this->repository->create( 136 | $requestPath, 137 | $targetPath, 138 | $type, 139 | $typeAttributes, 140 | $redirectType, 141 | $description, 142 | $unique 143 | ); 144 | } 145 | 146 | public function update(array $data, int $id): object 147 | { 148 | $updated = $this->repository->update($data, $id); 149 | 150 | $this->forgetById($id); 151 | 152 | return $updated; 153 | } 154 | 155 | public function regenerateRoute($urlRewrite): object 156 | { 157 | return $this->repository->regenerateRoute($urlRewrite); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Repositories/Interfaces/UrlRewriteInterface.php: -------------------------------------------------------------------------------- 1 | model = $model; 24 | } 25 | 26 | public function getModel(): object 27 | { 28 | return $this->model; 29 | } 30 | 31 | public function setModel(object $model): object 32 | { 33 | $this->model = $model; 34 | 35 | return $this; 36 | } 37 | 38 | public function find(int $id): ?object 39 | { 40 | return $this->model->find($id); 41 | } 42 | 43 | public function checkIfIdExists(int $id): bool 44 | { 45 | return $this->model->where('id', $id)->exists(); 46 | } 47 | 48 | public function checkIfRequestPathExists(string $url): bool 49 | { 50 | return $this->model->where('request_path', $url)->exists(); 51 | } 52 | 53 | public function getByRequestPath(string $url): ?object 54 | { 55 | return $this->model->where('request_path', $url)->first(); 56 | } 57 | 58 | public function getByTypeAndAttributes(string $type, array $attributes): ?object 59 | { 60 | return $this->model->getByTypeAndAttributes($type, $attributes)->first(); 61 | } 62 | 63 | public function getByTargetPath($url): ?object 64 | { 65 | return $this->model->where('target_path', $url)->first(); 66 | } 67 | 68 | public function all(): ?object 69 | { 70 | return $this->model->all(); 71 | } 72 | 73 | public function delete(int $id): bool 74 | { 75 | return $this->find($id)->delete(); 76 | } 77 | 78 | public function regenerateAll(): void 79 | { 80 | if (empty($this->getTypes())) { 81 | throw UrlRewriteRegenerationFailed::noConfiguration(); 82 | } 83 | 84 | foreach ($this->getTypes() as $type) { 85 | $this->regenerateRoutesFromType($type); 86 | } 87 | } 88 | 89 | public function regenerateRoutesFromType(string $type): void 90 | { 91 | if (! array_key_exists($type, $this->getTypes())) { 92 | throw UrlRewriteRegenerationFailed::invalidType($type); 93 | } 94 | 95 | $rewrites = $this->model->where('type', $type)->get(); 96 | 97 | foreach ($rewrites as $rewrite) { 98 | $this->regenerateRoute($rewrite); 99 | } 100 | } 101 | 102 | public function regenerateRoute(object $urlRewrite): object 103 | { 104 | if (! array_key_exists($urlRewrite->type, $this->getTypes())) { 105 | throw UrlRewriteRegenerationFailed::invalidType($urlRewrite->type); 106 | } 107 | 108 | if (! \is_array($urlRewrite->type_attributes)) { 109 | throw UrlRewriteRegenerationFailed::columnNotSet($urlRewrite, 'type_attributes'); 110 | } 111 | 112 | return $this->update( 113 | ['target_path' => $this->targetPathFromRoute($urlRewrite->type, $urlRewrite->type_attributes)], 114 | $urlRewrite->id 115 | ); 116 | } 117 | 118 | public function create( 119 | string $requestPath, 120 | ?string $targetPath, 121 | ?string $type = null, 122 | ?array $typeAttributes = null, 123 | int $redirectType = 0, 124 | ?string $description = null, 125 | ?bool $unique = false 126 | ): object { 127 | [$requestPath, $targetPath] = $this->validateCreate( 128 | $requestPath, 129 | $targetPath, 130 | $type, 131 | $typeAttributes, 132 | $redirectType, 133 | $unique 134 | ); 135 | 136 | return $this->model->create( 137 | [ 138 | 'type' => $type, 139 | 'type_attributes' => $typeAttributes, 140 | 'request_path' => $requestPath, 141 | 'target_path' => $targetPath, 142 | 'redirect_type' => $redirectType, 143 | 'description' => $description, 144 | ] 145 | ); 146 | } 147 | 148 | public function update(array $data, int $id): object 149 | { 150 | $record = $this->find($id); 151 | 152 | $record->update($data); 153 | 154 | return $record; 155 | } 156 | 157 | protected function generateUnique(string $requestPath, int $id = 1): string 158 | { 159 | if ($this->checkIfRequestPathExists($requestPath.'-'.$id)) { 160 | return $this->generateUnique($requestPath, $id + 1); 161 | } 162 | 163 | return $requestPath.'-'.$id; 164 | } 165 | 166 | protected function getTypes(): array 167 | { 168 | return config('url-rewrite.types'); 169 | } 170 | 171 | protected function targetPathFromRoute($type, $attributes): string 172 | { 173 | return route($type, $attributes, false); 174 | } 175 | 176 | protected function validateCreate( 177 | string $requestPath, 178 | ?string $targetPath, 179 | ?string $type, 180 | ?array $typeAttributes, 181 | int $redirectType, 182 | ?bool $unique 183 | ): array { 184 | if (! in_array($redirectType, self::allowedTypes, true)) { 185 | throw new \Exception('Redirect type must be 0, 1 or 2'); 186 | } 187 | 188 | if ($this->checkIfRequestPathExists($requestPath)) { 189 | if (! $unique) { 190 | throw UrlRewriteAlreadyExistsException::requestPath($requestPath); 191 | } 192 | 193 | $requestPath = $this->generateUnique($requestPath); 194 | } 195 | 196 | if ($targetPath === null && isset($type, $typeAttributes)) { 197 | $targetPath = $this->targetPathFromRoute($type, $typeAttributes); 198 | } 199 | 200 | return [$requestPath, $targetPath]; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 16 | if (! class_exists('CreateUrlRewriteTable')) { 17 | $timestamp = date('Y_m_d_His'); 18 | $this->publishes([ 19 | __DIR__.'/../database/migrations/create_url_rewrites_table.php.stub' => database_path('migrations/'.$timestamp.'_create_url_rewrites_table.php'), 20 | ], 'migrations'); 21 | } 22 | $this->publishes([ 23 | __DIR__.'/../config/url-rewrite.php' => config_path('url-rewrite.php'), 24 | ], 'config'); 25 | $this->publishes([ 26 | __DIR__.'/../nova/UrlRewrite.php.stub' => app_path('Nova/UrlRewrite.php'), 27 | ], 'nova'); 28 | } 29 | 30 | $this->loadTranslationsFrom(__DIR__.'/../resources/lang/', 'urlrewrites'); 31 | } 32 | 33 | public function register(): void 34 | { 35 | $this->mergeConfigFrom( 36 | __DIR__.'/../config/url-rewrite.php', 37 | 'url-rewrite' 38 | ); 39 | 40 | $this->registerRepository(); 41 | $this->registerFacade(); 42 | $this->registerRouteMacro(); 43 | } 44 | 45 | protected function registerRouteMacro(): void 46 | { 47 | $queryParam = '.*'; 48 | 49 | if (class_exists('Laravel\\Nova\\Nova')) { 50 | $novaPath = ltrim($this->app['config']['nova']['path'], '/'); 51 | $queryParam = "^(?!$novaPath).*"; 52 | } 53 | 54 | $router = $this->app['router']; 55 | $router->macro('rewrites', function () use ($router, $queryParam) { 56 | $router->get('{url}', '\\'.UrlRewriteController::class)->where('url', $queryParam)->name('url.rewrite'); 57 | }); 58 | } 59 | 60 | protected function registerRepository(): void 61 | { 62 | $this->app->singleton(UrlRewriteInterface::class, function () { 63 | $urlRewriteConfig = $this->app['config']['url-rewrite']; 64 | $repositoryClass = $urlRewriteConfig['repository']; 65 | $modelClass = $urlRewriteConfig['model']; 66 | 67 | $repository = new $repositoryClass(new $modelClass); 68 | 69 | if (! $urlRewriteConfig['cache']) { 70 | return $repository; 71 | } 72 | 73 | $cacheClass = $urlRewriteConfig['cache-decorator']; 74 | 75 | return new $cacheClass($repository, $this->app['cache.store']); 76 | }); 77 | } 78 | 79 | protected function registerFacade(): void 80 | { 81 | $this->app->bind(UrlRewrite::class, function () { 82 | return $this->app->make(UrlRewriteInterface::class); 83 | }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Traits/HasUrlRewrite.php: -------------------------------------------------------------------------------- 1 | getUrlRewrite()) { 14 | return ''; 15 | } 16 | 17 | return route('url.rewrite', $urlRewrite->request_path, false); 18 | } 19 | 20 | public function getUrlRewriteAttributesArray(): ?array 21 | { 22 | $mapped = []; 23 | 24 | foreach (config("url-rewrite.types.$this->urlRewriteType.attributes") as $attribute) { 25 | $mapped[$attribute] = $this->getAttribute($attribute); 26 | } 27 | 28 | return $mapped; 29 | } 30 | 31 | public function getUrlRewrite(): ?object 32 | { 33 | return UrlRewrite::getByTypeAndAttributes( 34 | config("url-rewrite.types.$this->urlRewriteType.route"), 35 | $this->getUrlRewriteAttributesArray() 36 | ); 37 | } 38 | } 39 | --------------------------------------------------------------------------------