Skip to content

Глава 5.2: Создание простых представлений для API

Время изучения: 45 минут


1. Задача: Визуализировать данные

Наш API в Главе 2 умеет отдавать данные в формате JSON. Это отлично для машин, но люди предпочитают видеть информацию на красиво оформленных веб-страницах. Наша цель — создать две такие страницы:

  1. Список всех планет (/planets)
  2. Страница одной планеты (/planets/{id})

Для этого мы будем использовать связку "Маршрут → Контроллер → Вид".

💡 Космическая аналогия:

Представьте, что JSON от API — это сырые данные телеметрии, просто поток цифр. Наша задача сегодня — создать в ЦУП два экрана (два "вида"):

  • Общий экран: показывает статус всех объектов в системе (список планет).
  • Детальный экран: при клике на объект выводит всю информацию о нём (страница одной планеты).

2. Шаг 1: Создание контроллера для веб-страниц

Для чистоты архитектуры не стоит смешивать логику API и логику веб-страниц в одном контроллере. Создадим новый контроллер специально для отображения наших Blade-видов.

Выполните в терминале:

php artisan make:controller Web/PlanetPageController
Мы создаем его в подпапке Web, чтобы отделить от API-контроллеров.

Откройте созданный файл app/Http/Controllers/Web/PlanetPageController.php.


Шаг 2: Страница со списком всех планет

1. Создаем метод в контроллере: В PlanetPageController добавьте метод index, который будет получать все планеты из базы данных и передавать их в вид.

<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Planet; // Не забудьте импортировать модель

class PlanetPageController extends Controller
{
    /**
     * Показывает страницу со списком всех планет.
     */
    public function index()
    {
        // 1. Получаем все планеты из БД
        $planets = Planet::all();

        // 2. Возвращаем вид и передаем в него данные
        return view('planets.index', ['planets' => $planets]);
    }
}

2. Создаем Blade-вид: Создайте файл resources/views/planets/index.blade.php. Мы будем использовать макет, созданный в прошлой главе.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Planets</title>
    <style>
        body {
            font-family: sans-serif;
            background-color: #f4f4f9;
            color: #333;
            margin: 0;
            padding: 2em;
        }
        .container {
            max-width: 960px;
            margin: 0 auto;
        }
        h2 {
            color: #1a202c;
        }
        hr {
            border: none;
            border-top: 1px solid #e2e8f0;
            margin: 1.5em 0;
        }
        .planet-list {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 1.5em;
        }
        .planet-card {
            background-color: #fff;
            border: 1px solid #e2e8f0;
            border-radius: 0.5em;
            padding: 1.5em;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            transition: transform 0.2s;
        }
        .planet-card:hover {
            transform: translateY(-5px);
        }
        .planet-card h3 {
            margin-top: 0;
            color: #2d3748;
        }
        .planet-card p {
            margin-bottom: 0.5em;
            color: #4a5568;
        }
        .planet-card a {
            color: #4299e1;
            text-decoration: none;
            font-weight: bold;
        }
        .planet-card a:hover {
            text-decoration: underline;
        }
        .no-planets {
            color: #718096;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>Список всех известных планет</h2>
        <hr>
        <div class="planet-list">
            @forelse($planets as $planet)
                <div class="planet-card">
                    <h3>{{ $planet->name }}</h3>
                    <p>Солнечная система: {{ $planet->solar_system }}</p>
                    <p>Диаметр: {{ number_format($planet->size_km, 0, '.', ' ') }} км</p>
                    <a href="/planets/{{ $planet->id }}">Узнать больше &rarr;</a>
                </div>
            @empty
                <p class="no-planets">В базе данных нет ни одной планеты. Пожалуйста, запустите сидеры.</p>
            @endforelse
        </div>
    </div>
</body>
</html>
  • number_format(...) — это обычная PHP-функция для красивого форматирования чисел. Её можно использовать прямо в Blade.

3. Создаем маршрут в routes/web.php:

use App\Http\Controllers\Web\PlanetPageController;

// ...

Route::get('/planets', [PlanetPageController::class, 'index']);
Теперь, если вы перейдете по адресу /planets в браузере, вы увидите страницу со списком планет!


4. Шаг 3: Страница одной планеты

1. Создаем метод в контроллере:

В PlanetPageController добавим метод show. Благодаря Route Model Binding, Laravel автоматически найдет планету по ID и передаст ее в метод.

<?php
// Внутри класса PlanetPageController
/**
 * Показывает страницу одной конкретной планеты.
 */
public function show(Planet $planet)
{
    // Laravel уже нашел для нас планету.
    // Если она не найдена, он автоматически вернет 404 ошибку.

    return view('planets.show', ['planet' => $planet]);
}

2. Создаем Blade-вид:

Создайте файл resources/views/planets/show.blade.php.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ $planet->name }}</title>
    <style>
        body {
            font-family: sans-serif;
            background-color: #f4f4f9;
            color: #333;
            margin: 0;
            padding: 2em;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }
        .container {
            max-width: 600px;
            width: 100%;
        }
        .planet-detail {
            background-color: #fff;
            border: 1px solid #e2e8f0;
            border-radius: 0.5em;
            padding: 2em;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
        }
        .planet-detail h1 {
            margin-top: 0;
            color: #2d3748;
        }
        .planet-detail p {
            margin-bottom: 1em;
            color: #4a5568;
            font-size: 1.1em;
        }
        .back-link {
            display: inline-block;
            margin-top: 1.5em;
            color: #4299e1;
            text-decoration: none;
            font-weight: bold;
        }
        .back-link:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="planet-detail">
            @if($planet->image_url)
                <img src="{{ $planet->image_url }}" alt="Image of {{ $planet->name }}" style="max-width: 100%; height: auto; border-radius: 0.5em; margin-bottom: 1em;">
            @endif
            <h1>{{ $planet->name }}</h1>
            @if($planet->description)
                <p>{{ $planet->description }}</p>
            @endif
            <p><strong>Солнечная система:</strong> {{ $planet->solar_system }}</p>
            <p><strong>Диаметр:</strong> {{ number_format($planet->size_km, 0, '.', ' ') }} км</p>
            <a href="/planets" class="back-link">&larr; Назад к списку планет</a>
        </div>
    </div>
</body>
</html>

3. Создаем маршрут в routes/web.php:

// Добавьте этот маршрут после маршрута для /planets
Route::get('/planets/{planet}', [PlanetPageController::class, 'show']);
- Имя параметра {planet} должно совпадать с именем переменной в методе контроллера (show(Planet $planet)) для корректной работы Route Model Binding.

Теперь, кликнув на ссылку "Узнать больше" на странице со списком, вы попадете на детальную страницу конкретной планеты.


Квиз для закрепления

1. Какой подход является лучшей практикой для разделения логики API и веб-страниц?

2. Что делает return view('planets.index', ['planets' => $planets]);?

3. Что такое Route Model Binding в контексте `show(Planet $planet)`?

4. Как в Blade можно отформатировать дату из поля `created_at`?

5. Если в маршруте указано `/posts/{post}`, как должна выглядеть сигнатура метода в контроллере для работы Route Model Binding?


🚀 Итог главы:

Вы успешно создали "витрину" для своего API, используя архитектуру Laravel MVC. Теперь у вас есть:

  • Отдельный контроллер для логики веб-страниц.
  • Динамическая страница со списком всех планет, получающая данные из БД.
  • Детальная страница для каждой планеты с использованием Route Model Binding.
  • Два веб-маршрута в routes/web.php для доступа к этим страницам.

Вы превратили сырые данные в понятную и полезную информацию для пользователя. В следующей главе мы добавим интерактивности, встроив JavaScript в наши Blade-виды для взаимодействия с API без перезагрузки страницы.