Laravel руководство на русском

Документация Laravel 9.x

Вольный перевод репозитория документации laravel/docs ветка 9.x на русский язык. Актуализация с основным репозиторием документации Laravel осуществляется не реже одного раза в месяц. Орфографические, пунктуационные и грубые смысловые ошибки исправляются по мере их выявления.

Перевод документации Laravel ветка 8.x.

Содержание документации

Перед чтением документации ознакомьтесь с соглашением по переводу.

Разделы, помеченные галочками, уже имеют актуализированные переводы полностью на русском языке.

  • Пролог

    • Примечания к релизу
    • Руководство по обновлению
    • Рекомендации по участию
  • Начало

    • Установка
    • Конфигурирование
    • Структура каталогов
    • Внешний интерфейс приложения
    • Стартовые комплекты
    • Развертывание
  • Архитектурные концепции

    • Жизненный цикл запроса
    • Контейнер служб
    • Поставщики служб
    • Фасады
  • Основы

    • Маршрутизация
    • Посредники
    • Предотвращение атак CSRF
    • Контроллеры
    • HTTP-запросы
    • HTTP-ответы
    • HTML-шаблоны
    • Шаблонизатор Blade
    • Объединение веб-активов
    • Генерация URL-адресов
    • Сессия HTTP
    • Валидация
    • Обработка ошибок
    • Логирование
  • Продвинутое руководство

    • Консоль Artisan
    • Трансляция событий
    • Кеш приложения
    • Коллекции
    • Контракты
    • События
    • Файловое хранилище
    • Глобальные помощники
    • HTTP-клиент
    • Локализация интерфейса
    • Почтовые отправления
    • Уведомления
    • Разработка пакетов
    • Очереди
    • Ограничители частоты
    • Планирование задач
  • Безопасность

    • Аутентификация
    • Авторизация
    • Подтверждение адреса электронной почты
    • Шифрование
    • Хеширование
    • Сброс пароля
  • База данных

    • Начало работы
    • Построитель запросов
    • Постраничная навигация
    • Миграции
    • Наполнение фиктивными данными
    • Использование Redis
  • Eloquent ORM

    • Начало работы
    • Отношения
    • Коллекции
    • Мутаторы и типизация
    • Ресурсы API
    • Сериализация
    • Фабрики
  • Тестирование

    • Начало работы
    • Тесты HTTP
    • Тесты консольных команд
    • Браузерные тесты
    • База данных
    • Имитация
  • Пакеты

    • Breeze – легковесная реализация аутентификации Laravel для ознакомления с функционалом. Включает простые шаблоны Blade, стилизованные с помощью Tailwind CSS. Содержит маршруты для публикации.
    • Dusk – автоматизация поведения браузера и тестирование с использованием ChromeDriver.
    • Envoy – инструмент для запуска задач, выполняемых на удаленных серверах. Задачи определяются в файле Envoy.blade.php в корне приложения с использованием директив шаблонизатора Blade.
    • Fortify – серверная реализация аутентификации Laravel. Не содержит никаких шаблонов. Используется в Laravel Jetstream.
    • Homestead – официальный образ Vagrant для приложений Laravel.
    • Horizon – панель управления и конфигурация очередей, использующих Redis.
    • Jetstream – красиво оформленный каркас приложений. Включает в себя Fortify и Sanctum.
    • Mix – гибкий API для определения шагов сборки Webpack; упрощает компиляцию и минимизацию файлов CSS и JavaScript.
    • Octane – повышает производительность вашего приложения с использованием мощных серверов Swoole и RoadRunner
    • Passport – реализация сервера OAuth2 для вашего приложения Laravel на основе League OAuth2.
    • Pint – is an opinionated PHP code style fixer for minimalists.
    • Sail – CLI для взаимодействия со средой разработки Docker.
    • Sanctum – легковесная система аутентификации для SPA (одностраничных приложений), мобильных приложений и простых API на основе токенов. Управление токенами API, аутентификация сессии. Не содержит никаких шаблонов. Используется в Laravel Jetstream.
    • Scout – «простое» решение на основе драйверов для добавления полнотекстового поиска моделям Eloquent.
    • Socialite – аутентификация через провайдеров OAuth: Facebook, Twitter, LinkedIn, Google, GitHub, GitLab и Bitbucket.
    • Telescope – панель управления, отображающая записи о произошедших в приложении событиях.
    • Valet – окружение разработки приложений Laravel для пользователей macOS.
  • Документация API

Содействие переводу

Официальная документация Laravel доступна только на английском языке. Для получения дополнительной информации о том, как вы можете внести свой вклад в перевод документации Laravel на русский язык, пожалуйста, прочтите Содействие в переводе документации.

Лицензия

Ссылка на лицензию оригинала документации laravel/docs.

Текущий перевод документации распространяется по лицензии MIT.

  • Пролог
    • Примечания к релизу
    • Руководство по обновлению
    • Рекомендации по участию
  • Начало работы
    • Установка
    • Конфигурация
    • Структура каталогов
    • Стартовые наборы
    • Развертывание
  • Архитектурные концепции
    • Жизненный цикл запроса
    • Сервис-контейнер
    • Сервис-провайдеры
    • Фасады
  • Основное
    • Маршрутизация
    • Посредники
    • CSRF Защита
    • Контроллеры
    • HTTP-запросы
    • HTTP-ответы
    • Представления
    • Шаблоны Blade
    • Генерация URL
    • Сессии
    • Валидация
    • Обработка ошибок
    • Логирование
  • Погружение
    • Artisan консоль
    • Широковещание
    • Кэширование
    • Коллекции
    • Компиляция JS и CSS
    • Контракты
    • События
    • Файловое хранилище
    • Помощники
    • HTTP Клиент
    • Локализация
    • Почта
    • Уведомления
    • Разработка пакетов
    • Очереди
    • Ограничение скорости
    • Планировщик
  • Безопасность
    • Аутентификация
    • Авторизация
    • Верификация email
    • Шифрование
    • Хеширование
    • Сброс пароля
  • База данных
    • Начало работы
    • Конструктор запросов
    • Пагинация
    • Миграции
    • Загрузка начальных данных
    • Redis
  • Eloquent ORM
    • Начало работы
    • Отношения
    • Коллекции
    • Мутаторы / Типизация
    • API Ресурсы
    • Сериализация
  • Тестирование
    • Начало работы
    • HTTP Тесты
    • Консольные Тесты
    • Браузерные Тесты
    • База данных
    • Имитация
  • Пакеты
    • Breeze
    • Cashier (Stripe)
    • Cashier (Paddle)
    • Dusk
    • Envoy
    • Fortify
    • Homestead
    • Horizon
    • Jetstream
    • Octane
    • Passport
    • Sail
    • Sanctum
    • Scout
    • Socialite
    • Telescope
    • Valet
    • API Документация
  1. 1. Введение
  2. 2. Установка
  3. 3. Подготовка базы данных

    1. 3.1. Миграции БД

      1. 3.1.1. Таблица users
      2. 3.1.2. Таблица tasks
    2. 3.2. Модели Eloquent

      1. 3.2.1. Модель User
      2. 3.2.2. Модель Task
    3. 3.3. Отношения Eloquent

      1. 3.3.1. Отношение tasks
      2. 3.3.2. Отношение user
  4. 4. Маршрутизация

    1. 4.1. Вывод представления
    2. 4.2. Аутентификация

      1. 4.2.1. Маршруты и представления аутентификации
      2. 4.2.2. Представления аутентификации
    3. 4.3. Контроллер задач

      1. 4.3.1. Аутентификация всех маршрутов задач
  5. 5. Создание макетов и представлений

    1. 5.1. Определяем макет
    2. 5.2. Определяем дочернее представление

      1. 5.2.1. Несколько поясняющих замечаний
  6. 6. Добавление задач

    1. 6.1. Проверка ввода

      1. 6.1.1. Переменная PHP$errors
    2. 6.2. Создание задачи
  7. 7. Отображение существующих задач

    1. 7.1. Внедрение зависимостей

      1. 7.1.1. Создание репозитория
      2. 7.1.2. Внедрение репозитория
    2. 7.2. Отображение задач
  8. 8. Удаление задач

    1. 8.1. Добавление кнопки удаления задачи

      1. 8.1.1. Примечание по спуфингу метода
    2. 8.2. Привязка модели маршрута
    3. 8.3. Авторизация

      1. 8.3.1. Создание политики
      2. 8.3.2. Авторизация действия
    4. 8.4. Удаление задачи

Этот перевод актуален для англоязычной документации на

08.12.2016

(ветка

5.2) и

19.06.2016

(ветка

5.1).
Опечатка? Выдели и нажми Ctrl+Enter.

Данная статья документации актуальна только для версий 5.2 и 5.1 и была удалена в версии 5.3.

Введение

Это руководство позволит вам быстро освоить фреймворк Laravel. Оно содержит информацию о миграциях баз данных, Eloquent ORM, маршрутизации, аутентификации, авторизации, валидации, представлениях и Blade-шаблонах. Это отличная отправная точка для новичков в фреймворке Laravel и PHP-фреймворках в целом. Если вы уже использовали Laravel или другие PHP-фреймворки, вы можете ознакомиться с нашими более продвинутыми руководствами.

Чтобы рассмотреть основной набор функций Laravel, мы создадим простой список задач и будем придерживаться его (типичный пример списка «to-do»). В отличие от базового данное руководство позволит пользователям создавать аккаунты и аутентифицироваться в приложении. Полный финальный вариант исходного кода для этого проекта доступен на GitHub.

Установка

Установка Laravel

Конечно, в первую очередь вам будет нужен свежий фреймворк Laravel. Чтобы запустить его, вы можете использовать виртуальную машину Homestead или локальную PHP-среду на ваш выбор. Как только ваше окружение будет готово, вы можете установить фреймворк Laravel, используя Composer:

shcomposer create-project laravel/laravel quickstart --prefer-dist

Установка проекта Quickstart (не обязательно)

Вы можете просто прочитать данное руководство. Однако, если вы хотите загрузить исходный код для этого руководства и выполнить его на локальной машине, то можете клонировать Git хранилище и установить зависимости:

shgit clone https://github.com/laravel/quickstart-intermediate quickstart
cd quickstart
composer install
php artisan migrate

Более полную информацию о создании локальной среды разработки Laravel вы сможете найти в документации по Homestead и по установке.

Подготовка базы данных

Миграции БД

Во-первых, давайте используем миграцию для определения таблицы базы данных для хранения всех наших задач. Миграции БД в Laravel позволяют простым способом определить структуру таблицы базы данных и выполнять модификации с использованием простого и выразительного PHP кода. Вместо того чтобы вручную добавлять столбцы в свои локальные копии БД, ваши товарищи по команде могут просто запустить миграции, которые вы поместили в систему управления версиями.

Таблица users

Поскольку мы решили, что пользователь может создать свой аккаунт в приложении, нам нужна таблица для хранениях пользователей. К счастью, Laravel уже поставляется с миграцией, включающей в себя базовую таблицу users. Поэтому нам не нужно вручную её создавать. По умолчанию миграция для таблицы users находится в каталоге database/migrations.

Таблица tasks

Теперь давайте создадим таблицу, которая будет содержать все наши задачи. Для создания различных классов может быть использован интерфейс Artisan. Он избавит вас от ручной генерации кода при создании проектов Laravel. Поэтому давайте используем команду shmake:migration для создания миграции новой базы данных для нашей таблицы tasks:

shphp artisan make:migration create_tasks_table --create=tasks

Миграция будет помещена в каталог database/migrations вашего проекта. Как вы могли заметить, команда shmake:migration уже добавила автоинкрементный ID и метки времени к файлу миграции. Давайте отредактируем этот файл и добавим дополнительный столбец name для имён наших задач, а также столбец user_id, который свяжет таблицу tasks с таблицей users:

PHP

<?phpuse IlluminateDatabaseSchemaBlueprint;
  use 
IlluminateDatabaseMigrationsMigration;

  class 

CreateTasksTable extends Migration
  
{
    
/**
     * Запуск миграций
     *
     * @return void
     */
    
public function up()
    {
      
Schema::create('tasks', function (Blueprint $table) {
        
$table->increments('id');
        
$table->integer('user_id')->unsigned()->index();
        
$table->string('name');
        
$table->timestamps();
      });
    }
/**
     * Откатить миграции
     *
     * @return void
     */
    
public function down()
    {
      
Schema::drop('tasks');
    }
  }

Чтобы запустить нашу миграцию, мы будем использовать команду Artisan shmigrate. Если вы используете Homestead, вы должны выполнить эту команду на своей виртуальной машине, так как у вашей host-машины не будет прямого доступа к базе данных:

shphp artisan migrate

Эта команда создаст все наши таблицы БД. Если вы просмотрите таблицы, используя какой-либо клиент для БД, вы должны заметить новую таблицу tasks, которая содержит столбцы, определённые в нашей миграции. Теперь мы готовы определить модели Eloquent ORM для наших задач!

Модели Eloquent

Eloquent — это стандартное ORM для Laravel (объектно-реляционное отображение). Eloquent делает безболезненным получение и хранение данных в вашей базе данных, используя чётко определённые «модели». Обычно, каждая Eloquent модель однозначно соответствует одной таблице базы данных.

Модель User

В первую очередь нам нужна модель, соответствующая нашей таблице users. Однако, если вы зайдете в папку app вашего проекта, вы увидите, что Laravel уже поставляется в комплекте с моделью User, поэтому нам не нужно создавать её вручную.

Модель Task

Давайте определим модель Task, которая будет соответствовать только что созданной нами таблице tasks. Мы снова можем использовать команду Artisan, чтобы сгенерировать эту модель. В этом случае мы будем использовать команду shmake:model:

shphp artisan make:model Task

Модель будет помещена в каталог app вашего приложения. По умолчанию класс модели пуст. Нам не надо явно указывать, какой таблице соответствует Eloquent модель, потому что подразумевается, что имя таблицы – это имя модели во множественном числе (s на конце). В этом случае предполагается, что модель Task соответствует таблице базы данных tasks.

Давайте добавим несколько вещей в эту модель. Для начала определим, что атрибут name этой модели должен быть массово присваиваемым. Это позволит нам заполнить атрибут name при использовании метода Eloquent PHPcreate():

PHP

<?phpnamespace App;

use 

IlluminateDatabaseEloquentModel;

class 

Task extends Model
{
  
/**
   * Массово присваиваемые атрибуты.
   *
   * @var array
   */
  
protected $fillable = ['name'];
}

Мы познакомимся с моделями Eloquent ближе, когда добавим маршруты к нашему приложению. Разумеется, вы можете заглянуть и в полную документацию по Eloquent для получения дополнительной информации.

Отношения Eloquent

Теперь, когда наши модели определены, нам нужно связать их. Например, наш User может иметь несколько Task, в то время как Task привязан к единственному User. Определение взаимосвязи позволит нам быстро проходить через наши отношения:

PHP

$user AppUser::find(1);

foreach (

$user->tasks as $task) {
  echo 
$task->name;
}

Отношение tasks

Во-первых, давайте определим отношение для нашей модели User. Отношения Eloquent определены как методы моделей. Eloquent поддерживает несколько различных типов отношений, с которыми можно ознакомиться в полной документации по Eloquent. Мы определим функцию PHPtasks в модели User, которая вызывает Eloquent-метод PHPhasMany():


+
5.2

добавлено в

5.2

(08.12.2016)

PHP

<?phpnamespace App;

use 

IlluminateFoundationAuthUser as Authenticatable;

class 

User extends Authenticatable
{
  
// Другие Eloquent свойства...

  /**
   * Получить все задачи пользователя.
   */

public function tasks()
  {
    return 
$this->hasMany(Task::class);
  }
}


+
5.1

добавлено в

5.1

(19.06.2016)

PHP

<?php
namespace App;
use 
IlluminateFoundationAuthUser as Authenticatable;
class 
User extends Authenticatable
{
  
// Другие Eloquent свойства...

  /**
   * Получить все задачи пользователя.
   */

public function tasks()
  {
    return 
$this->hasMany(Task::class);
  }
}

Отношение user

Теперь давайте определим отношение user для модели Tasks. И снова мы определим отношение как метод модели. В этом случае мы будем использовать Eloquent-метод PHPbelongsTo(), определяющий отношение:

PHP

<?phpnamespace App;

use 

AppUser;
use 
IlluminateDatabaseEloquentModel;

class 

Task extends Model
{
  
/**
   * Массово присваиваемые атрибуты.
   *
   * @var array
   */
  
protected $fillable = ['name'];/**
   * Получить пользователя - владельца данной задачи
   */
  
public function user()
  {
    return 
$this->belongsTo(User::class);
  }
}

Прекрасно! Теперь наши отношения определены, и мы можем начать создавать наши контроллеры!

Маршрутизация

В базовой версии нашего приложения мы определили всю нашу логику в файле routes.php, используя замыкания. В данном приложении мы будем использовать контроллеры для организации наших маршрутов. Контроллеры позволят нам лучше организовать логику HTTP-запросов в нескольких файлах.

Вывод представления

У нас будет один маршрут, использующий замыкание: наш маршрут /, представляющий из себя лендинг для гостей приложения. Давайте заполним наш маршрут /. По этому маршруту мы хотим отрисовывать HTML-шаблон, который содержит страницу «welcome».

В Laravel все HTML-шаблоны хранятся в каталоге resources/views, и мы можем использовать вспомогательную функцию PHPview(), чтобы возвратить один из этих шаблонов по нашему маршруту:

PHP

Route::get('/', function () {
  return 
view('welcome');
});

Конечно, нам необходимо создать это представление, давайте сделаем это!

Аутентификация

Помните, что мы также должны позволить пользователям создавать учётные записи и входить в наше приложение. Как правило, построение всего слоя аутентификации в веб-приложении является трудоёмкой задачей . Однако, так как это распространённая задача, Laravel попытался сделать эту процедуру абсолютно безболезненной.

Во-первых, обратите внимание, что app/Http/Controllers/Auth/AuthController уже включён в приложение Laravel. Этот контроллер использует специальный типаж (trait) AuthenticatesAndRegistersUsers со всей необходимой логикой для создания и аутентификации пользователей.

Маршруты и представления аутентификации

Итак, что нам осталось сделать? Нам всё ещё нужно создать шаблоны регистрации и входа в систему, а также определить маршруты, указывающие на контроллер аутентификации.


+
5.2

добавлено в

5.2

(08.12.2016)

Мы можем сделать это с помощью Artisan-команды shmake:auth:

shphp artisan make:auth

Теперь нам осталось только добавить маршруты аутентификации в наш файл маршрутов. Это можно сделать методом PHPauth() фасада Route, который зарегистрирует все необходимые нам маршруты для регистрации, входа и сброса пароля:

PHP

// Маршруты аутентификации...
Route::auth();

Когда маршруты auth зарегистрированы, проверьте, что свойство PHP$redirectTo контроллера app/Http/Controllers/Auth/AuthController имеет значение PHP/tasks:

PHP

protected $redirectTo '/tasks';

А также необходимо изменить в файле app/Http/Middleware/RedirectIfAuthenticated.php путь переадресации:

PHP

return redirect('/tasks');


+
5.1

добавлено в

5.1

(19.06.2016)

Для начала давайте добавим нужные нам маршруты в файл app/Http/routes.php:

PHP

// Маршруты аутентификации...
Route::get('auth/login''AuthAuthController@getLogin');
Route::post('auth/login''AuthAuthController@postLogin');
Route::get('auth/logout''AuthAuthController@getLogout');// Маршруты регистрации...
Route::get('auth/register''AuthAuthController@getRegister');
Route::post('auth/register''AuthAuthController@postRegister');

Представления аутентификации

Для аутентификации необходимо создать login.blade.php и register.blade.php в папке resources/views/auth. Конечно, дизайн и стиль этих представлений не имеет значения. Тем не менее, они должны содержать по крайней мере несколько основных полей.

Файл register.blade.php должен содержать форму, включающую в себя поля name, email, password и password_confirmation, эта форма должна создавать POST запрос к маршруту /auth/register.

Файл login.blade.php должен содержать форму, включающую в себя поля email и password, эта форма должна создавать POST запрос к маршруту /auth/login.

Если вы хотите просмотреть полные примеры для этих представлений, помните, что весь исходный код приложения доступен на GitHub.

Контроллер задач

Поскольку мы знаем, что нам нужно получать и сохранять задачи, давайте создадим TaskController с помощью командной строки Artisan, при этом новый контроллер будет помещён в папку app/Http/Controllers:


+
5.2

добавлено в

5.2

(08.12.2016)

shphp artisan make:controller TaskController


+
5.1

добавлено в

5.1

(19.06.2016)

shphp artisan make:controller TaskController --plain

Теперь, когда контроллер создан, давайте создадим стабы для некоторых маршрутов в нашем файле app/Http/routes.php, указывающих на контроллер:

PHP

Route::get('/tasks''TaskController@index');
Route::post('/task''TaskController@store');
Route::delete('/task/{task}''TaskController@destroy');

Аутентификация всех маршрутов задач

В данном приложении мы хотим, чтобы все наши маршруты задач требовали аутентификации пользователя. Другими словами, пользователь должен зайти в систему, чтобы создать задачу. Поэтому мы должны ограничить доступ к нашим маршрутам задач и открывать доступ только аутентифицированным пользователям. Laravel контролирует это, используя посредника.

Чтобы проверять аутентификацию пользователя в каждом действии, мы можем добавить вызов метода middleware в конструктор контроллера. Все доступные посредники маршрута определены в файле app/Http/Kernel.php. В данном случае мы хотим добавить посредник auth ко всем методам контроллера:

PHP

<?phpnamespace AppHttpControllers;

use 

AppHttpRequests;
use 
IlluminateHttpRequest;
use 
AppHttpControllersController;

class 

TaskController extends Controller
{
  
/**
   * Создание нового экземпляра контроллера.
   *
   * @return void
   */
  
public function __construct()
  {
    
$this->middleware('auth');
  }
}

Создание макетов и представлений

Основная часть этого приложения содержит одно представление с формой добавления новых задач, а также список всех текущих задач. Чтобы помочь вам визуализировать представление, мы сделали скриншот законченного приложения со стандартными стилями Bootstrap CSS:

/packages/proger/habravel/uploads/374-basic-overview.png

Определяем макет

Почти все веб-приложения используют один макет на всех своих страницах. Например, у нашего приложения есть верхняя панель навигации, которая присутствовала бы на каждой странице (если бы у нас их было больше одной). Laravel упрощает использование этих общих функций на всех страницах, используя макеты Blade.

Как мы выяснили ранее, все представления Laravel хранятся в resources/views. Давайте определим представление нового макета в resources/views/layouts/app.blade.php. Расширение .blade.php даёт фреймворку команду использовать механизм шаблонной обработки Blade, чтобы отрисовать это представление. Конечно, в Laravel вы можете использовать и простые PHP-шаблоны. Однако Blade позволяет быстро написать простой и небольшой шаблон.

Наше представление app.blade.php должно выглядеть примерно так:

xml

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Laravel Quickstart - Intermediate</title>

    
  </head>

  <body>
    <div class="container">
      <nav class="navbar navbar-default">
        
      </nav>
    </div>

    @yield('content')
  </body>
</html>

Обратите внимание на строчку xml@yield('content') в макете. Это специальная Blade-директива для указания всем дочерним страницам, наследующим этот шаблон, где они могут внедрить своё содержимое. Давайте определим дочернее представление, которое будет использовать этот макет и выводить его основной контент.

Определяем дочернее представление

Отлично. Макет нашего сайта завершён. Теперь мы должны определить представление, которое содержит форму создания новой задачи, а также таблицу со списком всех существующих задач. Давайте определим это представление в файле resources/views/tasks/index.blade.php, оно будет соответствовать методу index в нашем TaskController.

Мы пропустим небольшую часть Bootstrap CSS и сфокусируемся на важном. Помните, вы можете скачать весь исходный код этого приложения с GitHub:

PHP

<!-- resources/views/tasks/index.blade.php -->

@extends(

'layouts.app')

@

section('content')

  <!-- 

Bootstrap шаблон... -->

  <

div class="panel-body">
    <!-- 
Отображение ошибок проверки ввода -->
    @include(
'common.errors')

    <!-- 

Форма новой задачи -->
    <
form action="{{ url('task') }}" method="POST" class="form-horizontal">
      {{ 
csrf_field() }}

      <!-- 

Имя задачи -->
      <
div class="form-group">
        <
label for="task" class="col-sm-3 control-label">Задача</label>

        <

div class="col-sm-6">
          <
input type="text" name="name" id="task-name" class="form-control">
        </
div>
      </
div>

      <!-- 

Кнопка добавления задачи -->
      <
div class="form-group">
        <
div class="col-sm-offset-3 col-sm-6">
          <
button type="submit" class="btn btn-default">
            <
class="fa fa-plus"></iДобавить задачу
          
</button>
        </
div>
      </
div>
    </
form>
  </
div>

  <!-- 

TODOТекущие задачи -->
@
endsection

Несколько поясняющих замечаний

Прежде чем двигаться дальше, давайте немного поговорим об этом шаблоне. Во-первых, директива PHP@extends сообщает Blade, что мы используем макет, который мы определили в resources/views/layouts/app.blade.php. Все содержимое между PHP@section('content') и PHP@endsection будет добавлено вместо строчки директивы PHP@yield('content') в макете app.blade.php.

Директива PHP@include('common.errors') загрузит шаблон, расположенный в resources/views/common/errors.blade.php. Он пока не определён, но скоро мы это сделаем!

Итак, мы определили основной макет и представление для нашего приложения. Давайте вернём это представление из метода index контроллера TaskController:

PHP

/**
 * Отображение списка всех задач пользователя.
 *
 * @param  Request  $request
 * @return Response
 */
public function index(Request $request)
{
  return 
view('tasks.index');
}

Теперь мы готовы добавить код в наш метод контроллера маршрута POST /task, чтобы обработать входящие данные из формы и добавить новую задачу в БД.

Добавление задач

Проверка ввода

Теперь, когда у нас есть форма на нашем представлении, мы должны добавить код к нашему методу TaskController@store, чтобы проверить входящие данные из формы и создать новую задачу. Во-первых, давайте проверим ввод.

Для этой формы мы создадим обязательное поле name и зададим, что оно должно содержать не более 255 символов. Если проверка не пройдёт, то мы перенаправим пользователя назад к URL /tasks, а также возвратим ему в сессию его введённые данные с указанием на ошибки:

PHP

/**
 * Создание новой задачи.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
  
$this->validate($request, [
    
'name' => 'required|max:255',
  ]);
// Создание задачи...
}

Если вы создавали приложение по краткому руководству, то могли заметить, что код валидации там другой. Так как мы находимся в контроллере, мы можем использовать удобный типаж ValidatesRequests, который включён в базовый контроллер Laravel. Этот типаж представляет простой метод validate, который принимает запрос и массив правил валидации.

Нам даже не нужно самим определять результат валидации и даже не нужно вручную делать перенаправление. Если валидация не пройдена для заданных правил, пользователь будет автоматически перенаправлен туда, откуда он пришёл, и ошибки будут автоматически высвечены в сессии. Отлично!

Переменная PHP$errors

Помните, что мы использовали директиву PHP@include('common.errors') в нашем представлении, чтобы отобразить ошибки ввода формы. Представление PHPcommon.errors позволяет нам легко показывать ошибки ввода в одинаковом формате на всех наших страницах. Давайте определим содержимое этого представления:

PHP

<!-- resources/views/common/errors.blade.php -->

@if (

count($errors) > 0)
  <!-- 
Список ошибок формы -->
  <
div class="alert alert-danger">
    <
strong>УпсЧто-то пошло не так!</strong>

    <

br><br>

    <

ul>
      @foreach (
$errors->all() as $error)
        <
li>{{ $error }}</li>
      @endforeach
    </
ul>
  </
div>
@endif

Переменная PHP$errors доступна в любом представлении Laravel. Если не будет ошибок ввода, она просто будет пустым экземпляром PHPViewErrorBag.

Создание задачи

Теперь, когда обрабатывается ввод данных, давайте создадим новую задачу, продолжая заполнять наш маршрут. Как только новая задача будет создана, мы перенаправим пользователя назад к URL /tasks. Чтобы создать задачу, мы будем использовать мощность Eloquent отношений.

Большинство Laravel отношений предоставляют метод create, который принимает массив атрибутов и автоматически устанавливает значение внешнего ключа на соответствующей модели перед сохранением в базе данных. В этом случае метод create автоматически установит свойство user_id данной задачи на ID текущего аутентифицированного пользователя, к которому мы обращаемся с помощью PHP$request->user():

PHP

/**
 * Создание новой задачи.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
  
$this->validate($request, [
    
'name' => 'required|max:255',
  ]);
$request->user()->tasks()->create([
    
'name' => $request->name,
  ]);

  return 

redirect('/tasks');
}

Отлично! Теперь мы можем создавать задачи. Давайте продолжим создание нашего представления, добавив список всех существующих задач.

Отображение существующих задач

Во-первых, мы должны отредактировать наш метод TaskController@index, чтобы передать все существующие задачи в представление. Функция PHPview() принимает массив данных вторым параметром, который будет доступным для представления. Каждый ключ массива станет переменной в представлении. Например, мы можем сделать так:

PHP

  /**
   * Показать список всех задач пользователя.
   *
   * @param  Request  $request
   * @return Response
   */
public function index(Request $request)
{
  
$tasks $request->user()->tasks()->get();//для версии 5.1
  //$tasks = Task::where('user_id', $request->user()->id)->get();
return view('tasks.index', [
    
'tasks' => $tasks,
  ]);
}

Тем не менее, давайте рассмотрим некоторые возможности внедрения зависимостей от Laravel, чтобы внедрить TaskRepository в наш TaskController, который мы будем использовать для доступа ко всем нашим данным.

Внедрение зависимостей

Сервис-контейнер Laravel является одной из самых мощных возможностей всего фреймворка. После прочтения базового руководства, не забудьте прочитать всю документацию по контейнеру.

Создание репозитория

Как мы уже упоминали ранее, мы хотим определить TaskRepository, который содержит логику доступа ко всем данным для модели Task. Это будет особенно полезно, если приложение будет расти, и вам понадобится повсеместно использовать Eloquent запросы в приложении.

Итак, давайте создадим папку app/Repositories и добавим класс TaskRepository. Помните, что все app папки Laravel автоматически загружаются с помощью стандарта автоматической загрузки PSR-4, так что вы можете создать сколько угодно дополнительных каталогов:


+
5.2

добавлено в

5.2

(08.12.2016)

PHP

<?phpnamespace AppRepositories;

use 

AppUser;

class 

TaskRepository
{
  
/**
   * Получить все задачи заданного пользователя.
   *
   * @param  User  $user
   * @return Collection
   */
  
public function forUser(User $user)
  {
    return 
$user->tasks()
              ->
orderBy('created_at''asc')
              ->
get();
  }
}


+
5.1

добавлено в

5.1

(19.06.2016)

PHP

<?phpnamespace AppRepositories;

use 

AppUser;
use 
AppTask;

class 

TaskRepository
{
  
/**
   * Получить все задачи заданного пользователя.
   *
   * @param  User  $user
   * @return Collection
   */
  
public function forUser(User $user)
  {
    return 
Task::where('user_id'$user->id)
              ->
orderBy('created_at''asc')
              ->
get();
  }
}

Внедрение репозитория

Как только наш репозиторий определён, мы можем просто передать в конструктор наш TaskController и использовать его в нашем маршруте index. Так как Laravel использует контейнер, чтобы разрешить все контроллеры, наши зависимости будут автоматически внедрены в экземпляр контроллера:

PHP

<?phpnamespace AppHttpControllers;

use 

AppTask;
use 
AppHttpRequests;
use 
IlluminateHttpRequest;
use 
AppHttpControllersController;
use 
AppRepositoriesTaskRepository;

class 

TaskController extends Controller
{
  
/**
   * Экземпляр TaskRepository.
   *
   * @var TaskRepository
   */
  
protected $tasks;/**
   * Создание нового экземпляра контроллера.
   *
   * @param  TaskRepository  $tasks
   * @return void
   */
  
public function __construct(TaskRepository $tasks)
  {
    
$this->middleware('auth');$this->tasks $tasks;
  }
/**
   * Показать список всех задач пользователя.
   *
   * @param  Request  $request
   * @return Response
   */
  
public function index(Request $request)
  {
    return 
view('tasks.index', [
      
'tasks' => $this->tasks->forUser($request->user()),
    ]);
  }
}

Отображение задач

Когда данные переданы, мы можем обращаться к задачам в нашем представлении tasks/index.blade.php и выводить их на экран таблицей. Blade-конструкция PHP@foreach позволяет нам кратко писать циклы, которые компилируются в молниеносный простой PHP-код:

PHP

@extends('layouts.app')

@

section('content')
  <!-- 
Форма создания задачи... -->

  <!-- 

Текущие задачи -->
  @if (
count($tasks) > 0)
    <
div class="panel panel-default">
      <
div class="panel-heading">
        
Текущая задача
      
</div>

      <

div class="panel-body">
        <
table class="table table-striped task-table">

          <!-- 

Заголовок таблицы -->
          <
thead>
            <
th>Task</th>
            <
th>&nbsp;</th>
          </
thead>

          <!-- 

Тело таблицы -->
          <
tbody>
            @foreach (
$tasks as $task)
              <
tr>
                <!-- 
Имя задачи -->
                <
td class="table-text">
                  <
div>{{ $task->name }}</div>
                </
td>

                <

td>
                  <!-- 
TODOКнопка Удалить -->
                </
td>
              </
tr>
            @endforeach
          </
tbody>
        </
table>
      </
div>
    </
div>
   @endif
@
endsection

Наше приложение почти готово. Но у нас нет способа удалять наши существующие задачи, когда они завершены. Давайте добавим и это!

Удаление задач

Добавление кнопки удаления задачи

Мы оставили отметку «TODO» в коде, где предположительно будет находиться наша кнопка. Давайте добавим кнопку удаления к каждой строке нашего списка задач в представлении tasks/index.blade.php. Мы создадим маленькую однокнопочную форму для каждой задачи в списке. После нажатия кнопки приложению будет отправляться запрос DELETE /task, который будет обращаться к методу TaskController@destroy:


+
5.2

добавлено в

5.2

(08.12.2016)

PHP

<tr>
  <!-- 
Имя задачи -->
    <
td class="table-text">
    <
div>{{ $task->name }}</div>
  </
td>

  <!-- 

Кнопка Удалить -->
  <
td>
    <
form action="{{ url('task/'.$task->id) }}" method="POST">
      {{ 
csrf_field() }}
      {{ 
method_field('DELETE') }}

      <

button type="submit" id="delete-task-{{ $task->id }}" class="btn btn-danger">
        <
class="fa fa-btn fa-trash"></i>Удалить
      
</button>
    </
form>
  </
td>
</
tr>


+
5.1

добавлено в

5.1

(19.06.2016)

PHP

<tr>
  <!-- 
Имя задачи -->
   <
td class="table-text">
    <
div>{{ $task->name }}</div>
  </
td>

  <!-- 

Кнопка Удалить -->
  <
td>
    <
form action="/task/{{ $task->id }}" method="POST">
      {{ 
csrf_field() }}
      {{ 
method_field('DELETE') }}

      <

button>Удалить задачу</button>
    </
form>
  </
td>
</
tr>

Примечание по спуфингу метода

Обратите внимание на то, что method формы кнопки удаления объявлен как POST, несмотря на то, что мы отвечаем на запрос, используя маршрут PHPRoute::delete. HTML-формы позволяют использовать только GET и POST методы HTTP. А нам нужен способ имитировать запрос DELETE от формы.

Мы можем имитировать запрос DELETE, выводя результаты функции PHPmethod_field('DELETE') в нашей форме. Эта функция генерирует скрытый ввод формы, который распознается Laravel и используется, чтобы переопределить вызываемый метод HTTP. Сгенерированное поле будет похоже на это:

PHP

<input type="hidden" name="_method" value="DELETE">

Привязка модели маршрута

Теперь мы почти готовы определить метод PHPdestroy() в нашем TaskController. Но для начала давайте пересмотрим наше объявление маршрута и метод контроллера для этого маршрута:

PHP

Route::delete('/task/{task}''TaskController@destroy');


+
5.1

добавлено в

5.1

(19.06.2016)

Если не добавлять никакого дополнительного кода, то Laravel внедрит ID заданной задачи в метод TaskController@destroy:

PHP

/**
 * Уничтожить заданную задачу.
 *
 * @param  Request  $request
 * @param  string  $taskId
 * @return Response
 */
public function destroy(Request $request$taskId)
{
    
//
}

Однако, в первую очередь в этом методе мы должны будем получить экземпляр Task из базы данных, используя пришедший ID. Было бы неплохо, если б Laravel мог просто внедрить экземпляр Task, соответствующий этому ID? Давайте сделаем это возможным!
В нашем файле app/Providers/RouteServiceProvider.php в методе PHPboot() давайте добавим следующую строку:

PHP

$router->model('task''AppTask');

Эта небольшая строчка заставит Laravel извлечь модель Task, соответствующую заданному ID, каждый раз, когда он видит PHP{task} в объявлении маршрута. Теперь мы можем определить наш метод PHPdestroy():

PHP

/**
 * Уничтожить заданную задачу.
 *
 * @param  Request  $request
* @param  Task  $task
* @return Response
*/
public function destroy(Request $requestTask $task)
{
    
//
}


+
5.2

добавлено в

5.2

(08.12.2016)

Поскольку переменная PHP{task} в нашем маршруте совпадает с переменной PHP$task, определённой в методе нашего контроллера, неявная привязка модели Laravel автоматически внедрит экземпляр соответствующей модели Task.

Авторизация

Теперь у нас есть экземпляр Task, внедрённый в метод PHPdestroy(). Тем не менее, нет никакой гарантии того, что аутентифицированный пользователь на самом деле «владеет» данной задачей. Например, злоумышленник может сделать запрос на удаление задачи другого пользователя, передавая случайный ID задачи по URL /tasks/{task}. Поэтому мы должны использовать возможности авторизации Laravel, чтобы быть уверенным, что аутентифицированный пользователь на самом деле является владельцем экземпляра Task, который был внедрён в маршрут.

Создание политики

Laravel использует «политики» для организации логики авторизации в простых небольших классах. Как правило, каждая политика соответствует модели. Давайте создадим TaskPolicy, используя команду Artisan, которая поместит сгенерированный файл в app/Policies/TaskPolicy.php:

shphp artisan make:policy TaskPolicy

Следующим шагом будет добавление метода PHPdestroy() к политике. Этот метод получает экземпляр User и экземпляр Task. Метод должен просто проверить, соответствует ли ID пользователя user_id задачи. Фактически, все методы политики должны возвращать true или false:

PHP

<?phpnamespace AppPolicies;

use 

AppUser;
use 
AppTask;
use 
IlluminateAuthAccessHandlesAuthorization;

class 

TaskPolicy
{
    use 
HandlesAuthorization;/**
     * Определяем, может ли данный пользователь удалить данную задачу.
     *
     * @param  User  $user
     * @param  Task  $task
     * @return bool
     */
    
public function destroy(User $userTask $task)
    {
        return 
$user->id === $task->user_id;
    }
}

В конце нам надо связать нашу модель Task с TaskPolicy. Мы можем сделать это, добавив одну строчку к свойству PHP$policies в файле app/Providers/AuthServiceProvider.php. Она проинформирует Laravel о том, какая политика должна быть использована каждый раз, когда мы пытаемся авторизовать действие в экземпляре Task:

PHP

/**
 * Маппинг политики для приложения.
 *
 * @var array
 */
protected $policies = [
    
'AppTask' => 'AppPoliciesTaskPolicy',//для версии 5.1
    //Task::class => TaskPolicy::class,
];

Авторизация действия

Теперь, когда наша политика написана, давайте использовать ее в нашем методе PHPdestroy(). Все контроллеры Laravel могут вызвать метод PHPauthorize(), который представлен типажом AuthorizesRequest:

PHP

  /**
   * Уничтожение заданной задачи.
   *
   * @param  Request  $request
   * @param  Task  $task
   * @return Response
   */
  
public function destroy(Request $requestTask $task)
  {
      
$this->authorize('destroy'$task);// Удаление задачи...
}

Давайте немного исследуем этот вызов метода. Первым параметром, переданным методу PHPauthorize(), является имя метода политики, который мы хотим вызвать. Второй параметр — экземпляр модели, который нас сейчас интересует. Помните, мы недавно сообщили Laravel, что наша модель Task соответствует нашему TaskPolicy. Значит фреймворк знает, с какой политикой выполнить метод PHPdestroy(). Текущий пользователь будет автоматически отправлен в метод политики. Таким образом, мы не должны будем вручную передавать его здесь.

Если действие авторизовано, наш код будет продолжать выполняться как и всегда. Однако, если действие не авторизовано (метод политики PHPdestroy() вернул false), то будет выдано исключение 403, и на экран пользователя будет выведена страница ошибки.

Существует несколько других способов взаимодействия с сервисами авторизации от Laravel. Обязательно ознакомьтесь с ними в полной документации по авторизации.

Удаление задачи

Наконец, давайте добавим к нашему методу PHPdestroy() логику удаления текущей задачи. Мы можем использовать Eloquent-метод PHPdelete(), чтобы удалить заданный экземпляр модели в базе данных. Когда запись будет удалена, мы перенаправим пользователя назад к URL /tasks:

PHP

  /**
 * Уничтожение заданной задачи.
 *
 * @param  Request  $request
 * @param  Task  $task
 * @return Response
 */
public function destroy(Request $requestTask $task)
{
    
$this->authorize('destroy'$task);$task->delete();

    return 

redirect('/tasks');
}

DatabaseConsoleFactories

  1. FactoryMakeCommand (DatabaseConsoleFactories)
  2. FactoryMakeCommand::buildClass()
  3. FactoryMakeCommand::getOptions()
  4. FactoryMakeCommand::getPath()
  5. FactoryMakeCommand::getStub()
  6. FactoryMakeCommand::guessModelName()
  7. FactoryMakeCommand::resolveStubPath()

Опубликовано: четверг, 29 декабря 2022 г. в 09:59

  • Backend
  • Laravel

Если вы никогда не создавали Laravel приложение, позвольте показать вам пошаговое руководство по Laravel с нуля — без особых требований. Следуйте инструкциям, для изучения Laravel.

Laravel рос невероятными темпами с момента своего первого релиза и недавно добавили двух новых штатных сотрудников, которые помогают развивать экосистему фреймворка. Он никуда не денется в ближайшее время, так что мы могли бы попытаться изучить его, верно? Если вы ещё не сделали это. Laravel всегда был ориентирован на разработчиков, уделяя особое внимание опыту разработчиков, производительности и расширяемости. Если спросить любого разработчика Laravel, почему ему нравится Laravel, он почти всегда ответит, что это опыт разработчика. Итак, вопрос в том, зачем писать что-то ещё, когда на Laravel так приятно писать?!

Установка Laravel 9

Это руководство по Laravel предназначено для тех, кто только начал изучать Laravel. Может быть, они знают, что это такое, или пытались установить его пару раз и остановились, потому что почувствовали себя ошеломлёнными. Мы собираемся создать новое приложение Laravel с самого начала, и единственное, что вам нужно, это:

  • Терминал
  • Установленный PHP 8
  • Установленный и доступный в вашей директории Composer
  • Установленный и доступный в вашей директории NPM
  • Установленный SQLite

Так что же мы будем создавать? Мы собираемся сделать хранитель закладок, чтобы вы могли брать ссылки, которые вам интересны и сохранять их. Наряду с этим мы также позволим добавлять тэги к закладкам, для их классификации.

Как начать работу с Laravel? Первое, что нужно сделать, это, конечно же, создать новый проект и сделать это можно несколькими способами: через установщик Laravel, используя Laravel Sail Build или просто с помощью composer create-project. В этом руководстве по Laravel будем использовать composer create-project: хочу, чтобы требования оставались минимальными. Поэтому выберите каталог, в котором вы хотите разместить своё приложение, и выполните команду composer:

composer create-project laravel/laravel bookmarker

Теперь откройте новый каталог bookmarker в вашем редакторе кода, чтобы мы могли начать. Это пустой Laravel проект, наша отправная точка. Не буду делать никаких предположений о том, как вы хотите рассматривать этот проект локально, так как есть много разных вариантов. Вместо этого будем использовать artisan для обслуживания приложения. Запустите следующую команду artisan:

php artisan serve

Вы получите сообщение о том по какому адресу доступно ваше приложение. Откройте его в браузере. Это должен быть экран Laravel по умолчанию. Поздравляю, вы сделали первый шаг с Laravel! Далее мы перейдём к том, как работает это приложение.

Laravel загружает все маршруты из route/web.php, и у вас есть несколько вариантов маршрутизации. Вы можете загрузить представление напрямую, используя Route::view(), когда вам не нужно передавать данные в представление. Вы можете использовать вызов|Замыкание|функцию вызвав Route::get('route', fn () => view('home')) где get — HTTP-метод который вы хотите использовать. Вы также может использовать контроллеры, чтобы изолировать логику внутри одного класса Route::get('route', AppHttpControllersSomeRouteController::class).

По поводу загрузки маршрутов через контроллеры тоже есть варианты. Вы можете объявить их как строки и указать на определённые методы Route::get('route', 'AppHttpControllersSomeController@methodName'). Вы можете объявить ресурсы маршрута, где Laravel примет стандартный Route::resource('route', 'AppHttpControllersSomeController'), который будет сдержать методы index, create, store, show, edit, update и destroy. Они очень подробно объяснены в документации. Вы также можете использовать вызываемые контроллеры, представляющие собой класс с одним методом __invoke(), который обрабатывается как Замыкание Route::get('route', AppHttpControllersSomeController::class).

Подключение базы данных

В этом руководстве по Laravel будем использовать вызываемые контроллеры, так как мне нравиться их использовать. Мне нравиться использовать их, потому что они сохраняют мои маршруты в чистоте, удобны для моей IDE и инкапсулируют каждый маршрут в отдельный класс.

Начиная любой новый проект, вы должны понимать, что вы от него хотите. Как было сказано, мы создаём приложения для хранения закладок, поэтому мы можем предположить, что мы хотим, чтобы наше приложение делало. Напишем несколько требований:

  • Как пользователь, я хочу иметь возможность создавать новые закладки.
  • Как пользователь, я хочу просматривать все свои закладки.
  • Как пользователь, я хочу иметь возможность обновлять или удалять свои закладки.
  • Как пользователь, я хочу иметь возможность кликнуть на одну из своих закладок и посмотреть веб-сайт.
  • Как пользователь, я хочу иметь возможность просматривать закладки, отмеченные определёнными тегами.

Эти требования — почти пользовательские истории, поэтому мы можем их проработать и понять области, которые, возможно, захотим затронуть. Наш первый шаг — проектирование данных, которые нам нужно хранить в базе данных. Мы будем использовать SQLite для хранения данных этого приложения, чтобы наши требования оставались низкими.

Чтобы начать работать с SQLite в нашем Laravel приложении, во-первых, нам нужно создать SQLite файл в database/database.sqlite это можно сделать в терминале или IDE.

Затем нам нужно открыть .env файл и изменить блок database, что бы наше приложение знало о нашей базе данных. Laravel использует env файл для настройки локальной среды, которая будет загружена через различные файлы конфигурации в config/*.php. Каждый файл настраивает определённые части вашего приложения, поэтому не стесняйтесь потратить немного времени на изучение этих файлов и посмотрите как работает конфигурация.

На данный момент в вашем .env файле будет блок который выглядит следующим образом:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Теперь нам нужно внести в этот блок следующие изменения. Изменить DB_DATABASE на полный путь к SQLite файлу database/database.sqlite. В DB_CONNECTION указать sqlite и добавить строку DB_FOREIGN_KEYS=true. Остальное можно удалить, у вас должно получиться что-то вроде:

DB_CONNECTION=sqlite
DB_DATABASE=/Users/steve/code/sites/bookmarker/database/database.sqlite
DB_FOREIGN_KEYS=true

Мы установили соединение с базой данных SQLite. Установили путь к файлу базу и указали, что хотим, чтобы SQLite включил внешние ключи.

Когда у нас настроена и сконфигурирована база данных, мы можем запустить миграцию базы данных по умолчанию. В Laravel миграция базы данных используется для обновления состояния базы данных вашего приложения. Каждый раз, когда вы хотите изменить структуру базы данных, вы создаёте новую миграцию для создания таблицы, добавления или удаления полей, или даже полного удаления таблицы. Документация по миграции базы данных превосходна и объясняет все доступные варианты, поэтому когда будет время — обязательно её прочтите. Так же вам стоит прочитать статью Laravel: Все секреты миграции. По умолчанию Laravel поставляется с несколькими миграциями для пользователей, сброса пароля, неудачных заданий и PAT. Они полезны в 99% приложений, поэтому мы оставим их как есть.

К счастью, в Laravel есть готовая модель User, поэтому нам не нужно ничего редактировать или менять. Мы собираем имена пользователей, адреса электронной почты и пароли, сохраняем время создания и обновления пользователя. Итак, у нас есть уже готовая модель данных. Нам нужно подумать о том, как этот пользователь может получить доступ к нашему приложению. Нужно, что бы он мог войти в систему или зарегистрировать новую учётную запись. Для этого в Laravel есть несколько доступных пакетов, или вы можете создать свою собственную аутентификацию. Стандартные пакеты превосходны и настраиваются, поэтому будем использовать их.

Для этого приложения будем использовать Breeze, который представляет базовую основу аутентификации. Но есть и другие варианты, такие как Jetstream позволяющие использовать 2FA и модель команд, в которой могут сотрудничать несколько человек. Существует ещё один пакет под названием Sociality, который позволяет настраивать вход через социальные сети от множества поставщиков. Однако нам это не нужно, поэтому становите Laravel Breeze с помощью следующей команды:

composer require laravel/breeze --dev

Это делает Laravel Breeze зависимостью разработки для приложения, и это зависимость разработки, потому что её нужно установить. После установки, пакет копирует файлы в ваше приложение для маршрутизации, представлений, контроллеров и прочего. Итак, давайте установим пакет, используя следующую команду artisan:

php artisan breeze:install

Наконец, нам нужно установить и собрать фронтенд ресурсы с помощью npm:

npm install && npm run dev

Этот процесс займёт некоторое время, так как необходимо загрузить все пакеты JavaScript или CSS, затем запустить процесс сборки. Как только это будет сделано скрипт завершит свою работу.

Всё установлено и готово. Нужно запустить миграцию базы данных, что бы наша база находилась в определённом состоянии, с которым мы могли бы начать работать. Вы можете сделать это выполнив команду artisan:

php artisan migrate

Эта команда выполнит каждую миграцию, которая содержится в каталоге database/migrations, и применит их к базе данных. Таким образом, состояние базы данных можно напрямую связать с системой управления версиями, что сделает ваше приложение более умным и устойчивым.

Давайте на минуту задумаемся о том, как мы хотим хранить закладки. Каждая закладка должна принадлежать пользователю, иметь уникальный идентификатор, URL-адрес, который можно посетить, и необязательно описание на случай, если вы хотите написать заметку о том, для чего она нужна.

Создание моделей Eloquent

Теперь мы можем сгенерировать новую Eloquent модель и выполнить миграцию с помощью командной строки artisan. Выполните в терминале следующее:

php artisan make:model Bookmark -m

Мы говорим Laravel создать новую модель Eloquent с названием Bookmark, а флаг -m указывает также сгенерировать миграцию. Если вам когда-нибудь понадобится создать новую модель и выполнить миграцию, рекомендуется использовать этот подход, поскольку он выполняет и то, и другое одновременно. Вы так же можете добавить к этой команде другие флаги для создания фабрик моделей, наполнителей и т.д., но мы не будем использовать их в этом руководстве по Laravel.

Это создаст новую миграцию внутри database/migrations, у неё будет временная метка, за которой следует create_bookmarks_table. Откройте её в своей IDE, что бы мы могли структурировать данные. В методе up замените содержимое следующим блоком:

Schema::create('bookmarks', static function (Blueprint $table): void {
$table->id();

$table->string('name');
$table->string('url');
$table->text('description')->nullable();

$table->foreignId('user_id')
->index()->constrained()->cascadeOnDelete();

$table->timestamps();
});

Из приведённого выше фрагмента кода вы можете увидеть описательный характер миграции базы данных, то, как мы создадим новую таблицу, и описание того, как мы хотим, чтобы она была создана. Теперь мы можем применить эти изменения к нашей базе данных, вновь запустив команду миграции artisan:

php artisan migrate

Затем, давайте перейдём к нашей Eloquent Модели и добавим код, что бы она знала о полях базы данных и любых отношениях, которые они могут иметь. Откройте app/Models/Bookmark.php и замените содержимое следующим кодом:

declare(strict_types=1);

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsBelongsTo;

class Bookmark extends Model
{
use HasFactory;

protected $fillable = [
'name',
'url',
'description',
'user_id'
];

public function user(): BelongsTo
{
return $this->belongsTo(
related: User::class,
foreignKey: 'user_id',
);
}
}

Мы установили fillable атрибуты в соответствии с полями, доступными в таблице. Это остановит любые проблемы с массовым назначением атрибутов. Затем мы добавили метод user который является отношением. Запись Bookmark BelongsTo User (буквально: Закладка принадлежит Пользователю), использующему внешний ключ user_id. Мы можем добавить отношение к нашей модели User, поэтому в каталоге Models откройте файл User.php и замените содержимое следующим кодом:

declare(strict_types=1);

namespace AppModels;

use IlluminateContractsAuthMustVerifyEmail;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentRelationsHasMany;
use IlluminateFoundationAuthUser as Authenticatable;
use IlluminateNotificationsNotifiable;
use LaravelSanctumHasApiTokens;

class User extends Authenticatable
{
use Notifiable;
use HasFactory;
use HasApiTokens;

protected $fillable = [
'name',
'email',
'password',
];

protected $hidden = [
'password',
'remember_token',
];

protected $casts = [
'email_verified_at' => 'datetime',
];

public function bookmarks(): HasMany
{
return $this->hasMany(
related: Bookmark::class,
foreignKey: 'user_id',
);
}
}

Теперь пользователь знает об отношениях к закладкам как User HasMany Bookmarks (буквально: у Пользователя Много Закладок). Мы будем использовать эти отношения чуть позже, когда начнём строить логику в нашем приложении.

Наконец, мы можем создать модель Тэг. Мы хотим, что бы с каждой закладкой было связано много тэгов tags. Возьмём в качестве примера Laravel News. Мы могли бы поставить следующие тэги:

  • Laravel
  • News
  • Tutorials
  • Jobs

Таким образом, каждый раз когда мы хотим посмотреть закладки отмеченные любым из этих тэгов, должна появиться закладка Laravel News. Как и раньше, мы собираемся запустить команду artisan для создания модели Tag:

php artisan make:model Tag -m

Теперь откройте файл миграции в текстовом редакторе и снова замените содержимое метода up:

Schema::create('tags', static function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->string('slug')->unique();
});

У наших тэгов есть name и slug, на этот раз нам не нужны временные метки, так как это не важная информация. Я называю это мета моделью, используемой для категоризации и, в основном, системой, пользователь создаёт их, но они не в центре внимания.

Итак, давайте поработаем над Eloquent моделью Tag:

declare(strict_types=1);

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;

class Tag extends Model
{
use HasFactory;

protected $fillable = [
'name',
'slug',
];

public $timestamps = false;
}

Пока никаких отношений не настраиваем, нам понадобится сводная таблица, для связывания тэгов с закладками. Запустите команду генерации миграции в терминале:

php artisan make:migration create_bookmark_tag_table

В Laravel есть соглашение, согласно которому для названия для сводных таблиц вы задаёте имя указав связываемые таблицы в алфавитном порядке и единственном числе. Итак, мы хотим объединить таблицы bookmarks и tags, поэтому мы называем сводную таблицу bookmark_tag поскольку теги могут принадлежать множеству разных закладок, а закладки могут иметь много тегов.

Давайте заполним эту миграцию, чтобы увидеть, чем она отличается, снова сосредоточившись на методе up:

Schema::create('bookmark_tag', static function (Blueprint $table): void {
$table->foreignId('bookmark_id')->index()->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->index()->constrained()->cascadeOnDelete();
});

Эта таблица должна содержать внешние ключи для закладок и первичные ключи тэгов. Теперь у нас есть Eloquent модель для этой таблицы, поэтому мы добавляем отношения в модель ‘Tag’ и Bookmark.

Теперь ваша Модель Tag должна выглядеть следующим образом:

declare(strict_types=1);

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsBelongsToMany;

class Tag extends Model
{
use HasFactory;

protected $fillable = [
'name',
'slug',
];

public $timestamps = false;

public function bookmarks(): BelongsToMany
{
return $this->belongsToMany(
related: Bookmark::class,
table: 'bookmark_tag',
);
}
}

Ваша Модель Bookmark должна выглядеть так:

declare(strict_types=1);

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsBelongsTo;
use IlluminateDatabaseEloquentRelationsBelongsToMany;

class Bookmark extends Model
{
use HasFactory;

protected $fillable = [
'name',
'url',
'description',
'user_id'
];

public function user(): BelongsTo
{
return $this->belongsTo(
related : User::class,
foreignKey: 'user_id',
);
}

public function tags(): BelongsToMany
{
return $this->belongsToMany(
related: Tag::class,
table: 'bookmark_tag',
);
}
}

Наконец выполните миграцию для обновления состояния базы данных:

php artisan migrate

Создание пользовательского интерфейса

Теперь, когда наши модели Bookmark и Tag знакомы друг с другом, мы можем приступить к созданию пользовательского интерфейса! Мы не будем сосредотачиваться на отточенном пользовательском интерфейсе, поэтому не стесняйтесь проявлять творческий подход. Однако мы будем использовать tailwindcss.

Мы собираемся сделать большую часть нашей работы для закладок в маршруте панели управления (dashboard), созданном Laravel Breeze, поэтому, если вы посмотрите в routes/web.php вы должны увидеть следующее:

declare(strict_types=1);

use IlluminateSupportFacadesRoute;

Route::get('/', function () {
return view('welcome');
});

Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

Маршрут панели управления в настоящее время замыкание, и мы, вероятно, хотим преобразовать его в контроллер. Итак, давайте создадим новый контроллер, чтобы сохранить логику для панели управления, запустив следующую команду artisan:

php artisan make:controller DashboardController --invokable

Теперь давайте реорганизуем файл маршрутов, чтобы он стал немного чище:

declare(strict_types=1);

use IlluminateSupportFacadesRoute;

Route::view('/', 'welcome')->name('home');

Route::get(
'/dashboard',
AppHttpControllersDashboardController::class
)->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

Мы упростили маршрут home до view маршрута, а маршрут dashboard теперь указывает на контроллер. Откройте этот контроллер в редакторе, чтобы можно было вставить логику из приведённого ниже кода:

declare(strict_types=1);

namespace AppHttpControllers;

use AppModelsBookmark;
use IlluminateContractsViewView;
use IlluminateHttpRequest;

class DashboardController extends Controller
{
public function __invoke(Request $request): View
{
return view('dashboard', [
'bookmarks' => Bookmark::query()
->where('user_id', auth()->id())
->get()
]);
}
}

Как и раньше, всё что нам нужно сделать это прямо сейчас вернуть представление. Теперь давайте проверим это, запустив следующую команду artisan, для запуска вашего приложения:

php artisan serve

Теперь, если вы откроете своё приложение в браузере, вверху справа вы должны увидеть две ссылки Login и Register (Вход и Регистрация). Попробуйте зарегистрировать учётную запись и подождите пока она перенаправит в панель управления. Вы должны увидеть сообщение You’re logged in!Вы вошли!.

Фантастическая работа продолжается! У вас есть приложение Laravel обрабатывающее аутентификацию и модели данных в фоновом режиме, которые можно использовать для создания закладок и управления ими.

Laravel blade

Когда дело доходит до фронтэнда, могут появиться затруднения в выборе, так как существуют миллионы библиотек на JavaScript, есть обычный PHP и Blade, которые вы можете использовать. В этой части руководства мы сосредоточимся на использовании Laravel Blade, поскольку не хотим лишних сложностей или дополнительных пакетов на раннем этапе обучения.

Когда мы установили Laravel Breeze, мы получили несколько дополнительных файлов представления, и это здорово, поскольку он уже настроен. Для новой формы закладок я создам новый компонент Blade, который представляет собой отдельное представление view, которое мы можем использовать в нескольких местах.

Создайте анонимный компонент Blade, который является просто файлом представления, выполнив следующую команду:

php artisan make:component bookmarks.form --view

Затем внутри нашего resources/views/dashboard.blade.php проведём рефакторинг, что бы он выглядел следующим образом:

<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<x-bookmarks.form :bookmarks="$bookmarks" />
</div>
</div>
</div>
</x-app-layout>

Мы загружаем blade компонент, вызывая <x-bookmarks.form />, и вот как это работает: Все blade компоненты можно загружать добавляя к имени префикс x-. Если он расположен в подкаталоге, мы обозначаем каждый подкаталог точкой ., поэтому глядя на x-bookmarks.form, мы можем предположить, что он хранится в resources/views/components/bookmarks/form.blade.php. В нём мы собираемся сделать простой способ добавления новых закладок. Добавьте следующий (массивный) фрагмент кода внутрь компонента:

@props(['bookmarks'])
<div>
<div x-data="{ open: true }" class="overflow-hidden">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<div class="-ml-4 -mt-2 flex items-center justify-between flex-wrap sm:flex-nowrap">
<div class="ml-4 mt-2">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Your Bookmarks
</h3>
</div>
<div class="ml-4 mt-2 flex-shrink-0">
<a x-on:click.prevent="open = ! open" class="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span x-show="! open" x-cloak>Show Form</span>
<span x-show="open" x-cloak>Hide Form</span>
</a>
</div>
</div>
</div>
<div x-show="open" x-cloak class="divide-y divide-gray-200 py-4 px-4">
<div class="pt-8">
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">
Create a new bookmark.
</h3>
<p class="mt-1 text-sm text-gray-500">
Add information about the bookmark to make it easier to understand later.
</p>
</div>
<form id="bookmark_form" method="POST" action="{{ route('bookmarks.store') }}" class="mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
@csrf
<div class="sm:col-span-3">
<label for="name" class="block text-sm font-medium text-gray-700">
Name
</label>
<div class="mt-1">
<input type="text" name="name" id="name" class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md">
</div>
@error('name')
<p class="mt-2 text-sm text-red-500">
{{ $message }}
</p>
@enderror
</div>
<div class="sm:col-span-3">
<label for="url" class="block text-sm font-medium text-gray-700">
URL
</label>
<div class="mt-1">
<input type="text" name="url" id="url" class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md">
</div>
@error('url')
<p class="mt-2 text-sm text-red-500">
{{ $message }}
</p>
@enderror
</div>
<div class="sm:col-span-6">
<label for="description" class="block text-sm font-medium text-gray-700">
Description
</label>
<div class="mt-1">
<textarea id="description" name="description" rows="3" class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border border-gray-300 rounded-md"></textarea>
</div>
<p class="mt-2 text-sm text-gray-500">
Write any notes about this bookmark.
</p>
@error('description')
<p class="mt-2 text-sm text-red-500">
{{ $message }}
</p>
@enderror
</div>
<div class="sm:col-span-6">
<label for="tags" class="block text-sm font-medium text-gray-700">
Tags
</label>
<div class="mt-1">
<input
type="text"
name="tags"
id="tags"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
/>
<p class="mt-2 text-sm text-gray-500">
Add a comma separated list of tags.
</p>
@error('tag')
<p class="mt-2 text-sm text-red-500">
{{ $message }}
</p>
@enderror
</div>
</div>
<div class="sm:col-span-6">
<div class="pt-5">
<div class="flex justify-end">
<a x-on:click.prevent="document.getElementById('bookmark_form').reset();" class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Cancel
</a>
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
@forelse ($bookmarks as $bookmark)
<div>
<a href="#" class="block hover:bg-gray-50">
<div class="px-4 py-4 sm:px-6">
<div class="flex items-center justify-between">
<p class="text-sm font-medium text-indigo-600 truncate">
{{ $bookmark->name }}
</p>
</div>
<div class="mt-2 sm:flex sm:justify-between">
<div class="flex space-x-4">
@foreach ($bookmark->tags as $tag)
<p class="flex items-center text-sm text-gray-500">
{{ $tag->name }}
</p>
@endforeach
</div>
</div>
</div>
</a>
</div>
@empty
<a href="#" class="relative block w-full border-2 border-gray-300 border-dashed rounded-lg p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path></svg>
<span class="mt-2 block text-sm font-medium text-gray-900">
Create a new bookmark
</span>
</a>
@endforelse
</div>

У нас получился довольно большой компонент, который будет обрабатывать всю логику необходимую для создания закладки во фронтэнде. Начнём с того, что наш компонент объявляет какие атрибуты будут рассматриваться как переменные данных @props(['bookmarks']), которые может использовать компонент. Это просто переменные которые мы передаём, чтобы наш компонент знал о них. Затем у нас есть секция вверху, которая является секцией управления у неё есть заголовок и кнопка действия. Мы используем [Alpine.js](https://alpinejs.dev/) для базового JavaScript, который нам нужен — переключение видимости формы. Наша форма — стандартная HTML-форма, но мы отправляем её данные по маршруту, который нам ещё предстоит создать, мы скоро его добавим. Затем мы добавляем в форму новую blade директиву @csrf, эта директива предотвращает CSRF с которыми мы можем столкнуться, если другие сайты попытаются вмешаться и взломать нашу форму. Остальной код — просто разметка визуальных элементов, поэтому не стесняйтесь настраивать его по своему усмотрению. Следует отметить, что в настоящее время мы добавляем тэги используя список тэгов разделённый запятыми. Мы могли бы подойти к этому немного по другому, если бы использовали больше JavaScript или UI-библиотек. Далее у нас идут кнопки отмены и сохранения. Кнопка отмены — сбрасывает форму с помощью JavaScript, а кнопка отправки, как вы можете догадаться, отправляет форму.

Создание контроллера

Итак, теперь мы должны сохранить данные формы, скорее всего, ваша страница не загрузится, потому что маршрут ещё не определён — и это нормально. Мы собираемся его создать. Однако сначала нам нужно создать контроллер, в который мы собираемся сохранить эти данные. С помощью следующей команды artisan создайте новый контроллер:

php artisan make:controller Bookmarks/StoreController --invokable

Затем добавим в файл маршрутов следующее:

Route::post(
'bookmarks',
AppHttpControllersBookmarksStoreController::class,
)->middleware(['auth'])->name('bookmarks.store');

Теперь внутри контроллера нужно сделать несколько вещей, Во-первых, мы хотим проверить запрос, чтобы мы могли передать информацию обратно с сообщением валидации. Затем мы хотим выполнить действие для создания новой закладки, и наконец, перенаправить пользователя обратно в панель управления, где он увидит только что созданную закладку.

Внутри app/Http/Controllers/Bookmarks/StoreController.php добавим следующий код:

declare(strict_types=1);
namespace AppHttpControllersBookmarks;
use AppHttpControllersController;
use AppModelsTag;
use IlluminateHttpRequest;
use IlluminateHttpRedirectResponse;
class StoreController extends Controller
{
public function __invoke(Request $request): RedirectResponse
{
$this->validate($request, [
'name' => [
'required',
'string',
'min:1',
'max:255',
],
'url' => [
'required',
'url',
],
'description' => [
'nullable',
'string',
],
'tags' => [
'nullable',
'array',
]
]);
$bookmark = auth()->user()->bookmarks()->create([
'name' => $request->get('name'),
'url' => $request->get('url'),
'description' => $request->get('description'),
]);
foreach (explode(',', $request->get('tags')) as $tag) {
$tag = Tag::query()->firstOrCreate(
['name' => trim(strtolower($tag))],
);
$bookmark->tags()->attach($tag->id);
}
return redirect()->route('dashboard');
}
}

У нас есть метод __invoke, который примет текущий запрос. Он обрабатывается Laravel DI контейнером, так что вам не о чем беспокоиться. Основная причина, по которой мы вызываем $this->validate заключается в том, что мы расширяем основной Контроллер для нашего Laravel приложения. Установим правила валидации. Первый аргумент, передаваемый для валидации — данные, которые мы хотим проверить. Затем мы передаём массив правил валидации, которым нужно следовать. Я установил правила в соответствии с разумными значениями по умолчанию и не стеснялся использовать доступные опции валидации из документации Laravel.

Затем мы переходим к созданию нашей закладки. Мы не используем модель, так как можем сэкономить время, получая аутентифицированного пользователя, получая отношения закладок и вызывая create — — значит нам не нужно передавать user_id, так как он доступен непосредственно из auth()->user(). Затем мы циклически перебираем теги из запроса и либо получаем первый соответствующий, либо создаём новый по введённому имени (из которого мы удаляем лишние пробелы и преобразуем в нижний регистр для согласованности). Затем, мы прикрепляем этот новый тег к закладке. Наконец, мы возвращаем redirect()->route('dashboard'), чтобы перенаправить пользователя в панель управления с только что созданной закладкой.

Рефакторинг кода контроллера

Код хорош и делает именно то, что нам нужно, но можем ли мы его улучшить? Я думаю да.

Выносим валидацию в запрос формы

Первый шаг в рефакторинге кода — вынести валидацию из контроллера. Нет ничего плохого в том, чтобы хранить её в контроллере. Однако мы можем сэкономить немного времени, не создавая экземпляр контроллера в случае сбоя проверки. Для этого мы можем создать новый запрос формы, используя следующую команду artisan:

php artisan make:request Bookmarks/StoreRequest

Это создаст новый класс в app/Http/Requests/Bookmarks/StoreRequest.php. Давайте откроем его, добавим код и пройдёмся по нему:

declare(strict_types=1);
namespace AppHttpRequestsBookmarks;
use IlluminateFoundationHttpFormRequest;
class StoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => [
'required',
'string',
'min:1',
'max:255',
],
'url' => [
'required',
'url',
],
'description' => [
'nullable',
'string',
],
'tags' => [
'nullable',
'array',
]
];
}
}

Мы используем метод authorize() для проверки является ли авторизированным запрос. На данный момент это нормально, но если вы добавите уровень ролей и разрешений, позже вы сможете гарантировать, что аутентифицированному пользователю разрешено выполнять действие store в закладках. Затем у нас есть метод rules, массив правил проверки, подобный тому, как был у нас в контроллере. Что Laravel будет делать сейчас, используя контейнер DI, когда приходит запрос — прежде чем он создаст экземпляр нового контроллера, он попытается создать запрос формы. Это проверит запрос. Если валидация не пройдёт, будет выдано исключение, которое Laravel поймает, преобразует для вас в ErrorBagи вернутся к предыдущему представлению с этим пакетом ошибок, доступным для отображения любых ошибок валидации. Очень полезная функция Laravel. Но, прежде чем это произойдёт, нам нужно указать нашему контроллеру использовать новый запрос формы, поэтому изменим сигнатуру метода __invoke, следующим образом:

public function __invoke(StoreRequest $request): RedirectResponse

Неудачная валидация нас не затронет. Поэтому мы можем удалить расширение базового контроллера Laravel и удалить ручную проверку:

declare(strict_types=1);
namespace AppHttpControllersBookmarks;
use AppHttpRequestsBookmarksStoreRequest;
use AppModelsTag;
use IlluminateHttpRedirectResponse;
class StoreController
{
public function __invoke(StoreRequest $request): RedirectResponse
{
$bookmark = auth()->user()->bookmarks()->create([
'name' => $request->get('name'),
'url' => $request->get('url'),
'description' => $request->get('description'),
]);
foreach (explode(',', $request->get('tags')) as $tag) {
$tag = Tag::query()->firstOrCreate(
['name' => trim(strtolower($tag))],
);
$bookmark->tags()->attach($tag->id);
}
return redirect()->route('dashboard');
}
}

Внезапно наш контроллер стал намного меньше и его легче понять. Если вам нужно добавить примечания в комментариях, чтобы напомнить о выполнении валидации, не стесняйтесь, пока это не войдёт в рабочий процесс, в котором вы будете помнить что здесь происходит.

Выносим логику в Action

Мы могли бы оставить всё как есть, так как это разумное решение, но стандарт Laravel состоит в том, чтобы переместить логику создания в новый класс, называемый Action.

Не существует специальной команды для создания Action, нужно сделать это вручную. Создайте в каталоге app/Actions/Bookmarks/ новый файл CreateBookmarkAndTags.php.

Затем откройте этот файл в редакторе и добавьте следующий код:

declare(strict_types=1);
namespace AppActionsBookmarks;
use AppModelsBookmark;
use AppModelsTag;
class CreateBookmarkAndTags
{
public function handle(array $request, int $id): void
{
$bookmark = Bookmark::query()->create([
'name' => $request['name'],
'url' => $request['url'],
'description' => $request['description'],
'user_id' => $id,
]);
if ($request['tags'] !== null) {
foreach (explode(',', $request['tags']) as $tag) {
$tag = Tag::query()->firstOrCreate(
['name' => trim(strtolower($tag))],
);
$bookmark->tags()->attach($tag->id);
}
}
}
}

У нас есть один метод handle() принимающий данные запроса и идентификатор, который мы будем использовать в качестве идентификатора пользователя, затем копируем логику из контроллера и вносим несколько незначительных изменений. Мы можем использовать этот action класс в любом месте нашего приложения, из пользовательского интерфейса, из командной строки, или даже API, если это необходимо. Мы создали модульно действие, которое можно легко вызвать, протестировать и получить предсказуемые результаты.

Итак, теперь мы можем ещё больше облегчить контроллер:

declare(strict_types=1);
namespace AppHttpControllersBookmarks;
use AppActionsBookmarksCreateBookmarkAndTags;
use AppHttpRequestsBookmarksStoreRequest;
use IlluminateHttpRedirectResponse;
class StoreController
{
public function __invoke(StoreRequest $request): RedirectResponse
{
(new CreateBookmarkAndTags())->handle(
request: $request->all(),
id: auth()->id(),
);
return redirect()->route('dashboard');
}
}

Теперь у нас есть один action класс, который мы вызываем внутри контроллера, а затем возвращаем перенаправление. Намного чище и с хорошим названием. Конечно, мы можем пойти дальше, если захотим. Используя контейнер Laravel для внедрения action в конструктор — это позволит нам вызывать action класс. Это будет выглядеть следующим образом.

declare(strict_types=1);
namespace AppHttpControllersBookmarks;
use AppActionsBookmarksCreateBookmarkAndTags;
use AppHttpRequestsBookmarksStoreRequest;
use IlluminateHttpRedirectResponse;
class StoreController
{
public function __construct(
protected CreateBookmarkAndTags $action,
) {}
public function __invoke(StoreRequest $request): RedirectResponse
{
$this->action->handle(
request: $request->all(),
id: auth()->id(),
);
return redirect()->route('dashboard');
}
}

Этот последний метод полезен, если у вашего action класса есть требования к его конструктору. Скажем вы используете шаблон Репозитория или другой шаблон — вы можете добавить его в конструктор своего action и Laravel решит это автоматически, если сможет.

Создание контроллера удаления закладок

Таким образом, мы можем показать список и добавить закладки, и мы можем добавить в список кнопку удаления — не смысла создать что-то слишком больше, верно?

Создадим новый контроллер следующей командой:

php artisan make:controller Bookmarks/DeleteController --invokable

Теперь нам не нужен action, так как это однострочное действие, но если вы хотите предоставить несколько способов удаления закладок, выполните те же действия, что и выше, но на этот раз удалите закладку, а не создайте её. Добавьте следующий код в контроллер:

declare(strict_types=1);
namespace AppHttpControllersBookmarks;
use AppModelsBookmark;
use IlluminateHttpRedirectResponse;
use IlluminateHttpRequest;
class DeleteController
{
public function __invoke(Request $request, Bookmark $bookmark): RedirectResponse
{
$bookmark->delete();
return redirect()->route('dashboard');
}
}

Здесь мы принимаем модель Bookmark в качестве аргумента, чтобы мы могли включить привязку модели маршрута, где Laravel будет искать запись для вас и внедрять её в ваш метод — в случае сбоя он выдаст исключение 404. Всё что нам нужно сделать, это вызвать удаление модели и вернуть перенаправление. Добавьте следующий маршрут:

Route::delete(
'bookmarks/{bookmark}',
AppHttpControllersBookmarksDeleteController::class,
)->middleware(['auth'])->name('bookmarks.delete');

Наконец, мы можем вернуться к нашему компоненту и добавить кнопку:

@forelse ($bookmarks as $bookmark)
<div>
<a href="#" class="block hover:bg-gray-50">
<div class="px-4 py-4 sm:px-6">
<div class="flex items-center justify-between">
<p class="text-sm font-medium text-indigo-600 truncate">
{{ $bookmark->name }}
</p>
<div class="ml-2 flex-shrink-0 flex">
<form method="DELETE" action="{{ route('bookmarks.delete', $bookmark->id) }}">
@csrf
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-red-500 bg-gray-100 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Delete
</button>
</form>
</div>
</div>
<div class="mt-2 sm:flex sm:justify-between">
<div class="flex space-x-4">
@foreach ($bookmark->tags as $tag)
<p class="flex items-center text-sm text-gray-500">
{{ $tag->name }}
</p>
@endforeach
</div>
</div>
</div>
</a>
</div>
@empty
<a href="#" class="relative block w-full border-2 border-gray-300 border-dashed rounded-lg p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path></svg>
<span class="mt-2 block text-sm font-medium text-gray-900">
Create a new bookmark
</span>
</a>
@endforelse

Добавляем контроллера перехода по закладке

Теперь мы всё связали. Мы можем создать список и удалить. Последнее, что я сделаю, это добавлю способ просмотра закладок в представление. Простым способом сделать это было бы добавить кнопку, позволяющую открывать ссылку в новой вкладке, но это было бы скучно…

Вместо этого я буду перенаправлять закладку с реферером, что бы люди могли отслеживать, откуда она исходит. Для этого снова создадим контроллер с помощью консольной команды artisan:

php artisan make:controller Bookmarks/RedirectController --invokable

Добавим GET маршрут:

Route::get(
'bookmarks/{bookmark}',
AppHttpControllersBookmarksRedirectController::class
)->middleware(['auth'])->name('bookmarks.redirect');

Управление созданием этого URL-адреса мы могли бы написать вручную. Однако я создал библиотеку для таких ситуаций под названием juststeveking/uri-builder, которая позволит мне создавать URI и свободно добавлять дополнительные части.

declare(strict_types=1);
namespace AppHttpControllersBookmarks;
use AppHttpControllersController;
use AppModelsBookmark;
use IlluminateHttpRequest;
use JustSteveKingUriBuilderUri;
class RedirectController extends Controller
{
public function __invoke(Request $request, Bookmark $bookmark)
{
$url = Uri::fromString(
uri: $bookmark->url,
)->addQueryParam(
key: 'utm_campaign',
value: 'bookmarker_' . auth()->id(),
)->addQueryParam(
key: 'utm_source',
value: 'Bookmarker App'
)->addQueryParam(
key: 'utm_medium',
value: 'website',
);
return redirect(
$url->toString(),
);
}
}

Вам не нужно углубляться так сильно, как это делаю я — это часть зависит от вас. Наконец, мы можем просто добавить ссылку в пользовательский интерфейс и всё готово.

<div class="flex items-center justify-between">
<p class="text-sm font-medium text-indigo-600 truncate">
{{ $bookmark->name }}
</p>
<div class="ml-2 flex-shrink-0 flex">
<a
href="{{ route('bookmarks.redirect', $bookmark->id) }}"
target="__blank"
rel="nofollow noopener"
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-indigo-600 bg-gray-100 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>Visit</a>
<form method="POST" action="{{ route('bookmarks.delete', $bookmark->id) }}">
@csrf
@method('DELETE')
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-red-500 bg-gray-100 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Delete
</button>
</form>
</div>
</div>

Вы можете пойти дальше, собрав статистику о том, какие закладки вы просматривали больше всего, или что-то ещё. Вы даже можете позволить кликать по тэгам, что бы посмотреть все закладки связанные с данным тэгом, но я думаю этого будет достаточно для данного руководства, чтобы понять основы.


Как вам это руководство? Написание руководства для людей незнакомых с Laravel может быть сложной задачей, и я надеюсь, мне удалось объяснить основы достаточно хорошо и подробно, что бы вы могли следовать ему.

Понравилась статья? Поделить с друзьями:
  • В годы когда руководство страной возглавил хрущев
  • Производственно социальное руководство
  • Амоксиклав инструкция по применению цена суспензия для детей дозировка
  • Samsung clp 310 сервис мануал
  • Как подключить ватсап на планшет бесплатно пошаговая инструкция