Работа с динамическими страницами в Laravel

Опубликовано 993 просмотра
Работа с динамическими страницами в Laravel

Привет.

Очень часто, я бы даже сказал практически в каждом проекте возникает необходимость создать "динамические страницы" - страницы с одной сущностью, разным содержимым и без какого-либо паттерна для конфигурации конечного адреса, по которому сущность будет отображаться. Конечный адрес может иметь неограниченное количество уровней - от банального /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. Удачи.