
Привет.
Очень часто, я бы даже сказал практически в каждом проекте возникает необходимость создать "динамические страницы" - страницы с одной сущностью, разным содержимым и без какого-либо паттерна для конфигурации конечного адреса, по которому сущность будет отображаться. Конечный адрес может иметь неограниченное количество уровней - от банального /apple
до /fruits/apple/features
.
Самым простым вариантом который, вероятно, удовлетворит потребности большинства, будет указание следующего паттерна для переменной роута:
Route::get('{path}', '[email protected]')->where('path', '[0-9A-Za-z\/-]+');
Данный вариант прост и вероятнее всего многие на нем и остановятся, но имеет один недостаток - этот триггер сработает на все объявленные позднее роуты. Иногда достаточно объявить его в конце файла routes/web.php
но при подключении, например, пакетов-админпанелей со своими роутами они корректно выполняться не будут. Кроме того, этот вариант увеличит нагрузку на сервер, поскольку любой запрос к серверу, не подходящий под условия других роутов будет им обрабатываться, соотвественно делать запрос вида select * from posts where path = $path limit 1
в хранилище.
Довольно давно я написал решение, которое использую до сих пор. Его суть - через отдельный класс подгружать доступные страницы из хранилища и прописывать роуты "на лету".
Первым делом создадим таблицу в хранилище с колонками: имя name
, путь до страницы path
и содержимое страницы content
. Кроме этих полей обычно присутствуют SEO-настройки и поддержка разных шаблонов для страниц, но не будем фокусировать на этом внимание.
use Illuminate\Support\Facades\Schema;
Schema::create('pages', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 150);
$table->string('path')->unique()->index();
$table->text('content');
$table->timestamps();
});
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Class Page
* @package App\Models
* @property int $id
* @property string $name
* @property string $path
* @property string $content
* @property Carbon $created_at
* @property Carbon $updated_at
*/
class Page extends Model
{
protected $fillable = [
'name', 'path', 'content'
];
}
Добавим новый класс PageRoutes
, при помощи которого будем получать список страниц и заниматься конфигурацией роутов.
namespace App\Editing;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Carbon\Carbon;
use App\Http\Controllers\Visible\PagesController;
use App\Models\Page;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
/**
* Class PageRoutes
* @package App\Editing
*/
class PageRoutes
{
/**
* Define routes.
*/
public function routes()
{
$this->pages()->each(function(Page $page) {
Route::get($page->path, function() use ($page) {
return App::make(PagesController::class)->callAction('dynamic', ['page' => $page]);
})->name('page.'.Str::snake($page->path));
});
}
/**
* Load pages from database.
* @return Collection|Page[]
*/
private function pages()
{
return Cache::remember(
'pages_routes',
Carbon::now()->addWeek(),
function() {
try {
$pages = Page::all();
return $pages;
}
catch (\Exception $exception) {
return new Collection();
}
}
);
}
}
Приватный метод pages()
получает, кэширует и возвращает список страниц. Не забывайте во время добавления, изменения или удаления страниц сбрасывать кэш по ключу pages_routes
.
Публичный метод routes()
отвечает за добавление роутов на основе полученных в pages()
страниц. Циклом он проходится по доступным страницам и добавляет GET-роуты для каждой страницы. Вызов конечного метода dynamic
контроллера PagesController
происходит в анонимной функции. Так как страница уже получена, дабы не делать лишний запрос в метод сразу передается аргумент Page $page
.
Создадим контроллер.
namespace App\Http\Controllers\Visible;
use App\Http\Controllers\Controller;
use App\Models\Page;
use Illuminate\View\View;
/**
* Class PagesController
* @package App\Http\Controllers\Visible
*/
class PagesController extends Controller
{
/**
* @param Page $page
* @return View
*/
public function dynamic(Page $page)
{
return view('visible.pages.dynamic')->with(compact('page'));
}
}
И наконец, добавим вызов метода routes()
из PageRoutes
в web/routes.php
.
app(App\Editing\PageRoutes::class)->routes();
На этом все. Теперь, создав модель с любым path
, например /foo
по адресу http://localhost/foo
вы выведете созданную страницу.
Непосредственно про CRUD я говорить не буду, напомню лишь что не надо забывать о валидации path
. Удачи.