laravel-watch
Laravel для новичков
Stack: Laravel, Blade Templates, PostgreSQL
- Установка Laravel Herd
- Установка PHP через Laravel Herd
- Создаем новый проект через консоль laravel new
- Подключаем проект к Herd для работы с встроенным сервером
- Создаем сервер БД PostgreSQL
- Создаем БД
- Настраеваем .env файл
- Подключаемся к БД
- Запускаем миграции php artisan migrate
- herd unlink закроет доступ к приложению в браузере
- Запуск защищенного соединения (TLS сертификат)с проектом herd secure
- Для более удобной работы с Laravel проектами рекомендуется к установке плагин Laravel Idea
- Стандартный форматер кода
- Настраеваем форматер Pint от Laravel
- Устанавливаем Tilewind CSS
- Настраиваем tailwind.config.js
- Устанавливаем npm зависимости (npm install)
- Запускаем Vite сервер (npm run dev)
- Подключаем стили tailwind @vite(.../app.css)
- Указываем роут для тестирования подключения Tailwind
- Пишем html структуру
- Добавляем иконку с сайта fontawesome.com в качестве логотипа
- Указываем стили для логотипа при помощи класса "size-12"
- Верстка меню навигации и определяем стили
- Задаем контейнер для контента
- Указываем настройки для контейнера в файле tailwind.config.js для центровки (theme:{container:{center:true}})
- Задаем шапке fixed свойства
- Двойные фигурные скобки в blade {{ | }} - Вывод экранированных данных с дополнительной защитой от XSS атак
- config('app.name') - вывод значения APP_NAME из config -> app.php -> env -> APP_NAME
- Присваивание имени пути Route::view('/', 'index')->name('index'); для обращения к пути через route('index')
- GET обращение к маршруту /courses и возврат строкового значения из функции
- Рефакторим функцию в стрелочную для улучшения читаемости кода
- Создаем маршруты для всех страниц сайта
- Создаем папку components для хранения blade компонентов
- Создаем компонент при помощи функционала Laravel Idea, create view
- Создаем компонент layout.blade.php
- Связываем Content c {{ $slot }}. В $slot падает весь контент, который был определен в связь организуется по названию компонентов и тега
- Создаем компоненты для всех роутов
- Разбиваем разметку хедера на компоненты
- Выделяем ссылки навигации в массив при помощи директивы @php
- Выводим ссылки при помощи директивы @foreach
- В компоненте nav.blade мы определяем с атрибутом :href (с двоеточием), который в последствии упадет в {{ $attributes }} указанную в компоненте nav-item
- Сокращаем :href так как имя атрибута совпадает с именем переменной, которую мы помещаем в качестве атрибута
- Пишем структуру футера. Копирайт (©), дата (date('Y')), меню, социальные сети
- Повторяем тот же вывод навигации как в шапка
- Определяем маршруты. Для выделения повторяющихся слов используем Alt+J
- Добавляем файлы с шаблонами новых маршрутов
- Задаем стили для навигации в футере
- Центрируем контент
- Добавляем иконки соцсетей
- Создаем директорию footer. Файл footer.blade.php перемещаем в созданную директорию и переименовываем в index.blade.php таким образом адрес
к файлу footer находящийся в layout (x-footer) можно не менять.
- Отделяем copyright
- Переносим nav в директорию footer. Указываем Blade адрес
- Сбрасываем кеш Blade командой php artisan view:clear
- Выносим nav в отдельную директорию
- Оптимизируем nav использующийся в header и footer при помощи пропса :nav-items="$navItems"
- Объединяем массивы с повторяющимися данными в корневом файле layout
- Передаем массив в компоненты header и footer через props (инструмент передачи динамических данных) :nav-items="$navItems". Двоеточие перед именем пропса (nav-items) указывает, что мы передаем динамическое значение
- Используем сокращенную нотацию для пропса :$navItems
- Фильтруем массив для отображения конкретных данных в header и footer (В футере у нас отличаются два пункта)
- Используем laravel helper collect для создания коллекции из нашего массива для более гибкой работы с ним
- Используем laravel метод reject. Метод reject фильтрует коллекцию, используя заданное замыкание. Замыкание должно возвращать, true если элемент должен быть удален из результирующей коллекции:
- Передаем отфильтрованную коллекцию дальше в дочерний элемент (nav)
- Задаем route() пропсу :href чтобы исключить данную функцию из нашего массива и следовательно из метода reject
- Оптимизируем вызовы хелпера collect, один раз обернув исходный массив, вместо оборачивания отдельно в футере и хедере
- В файле items отделяем переменную attributes в блок @php
- Присваиваем переменной attributes значения по умолчанию через метод $attributes->merge(['class'])
- Элементу class присваиваем стили по умолчанию через merge
- Применяем условное слияние классов через метод class()
- Метод class, принимающий массив классов, где ключ массива содержит класс или классы ('px-4, 'flex'), которые вы хотите добавить, а значение является логическим выражением.
- Метод class применит наш класс указанный в ключе при выполнении условия true указанном в значении
- Добавляем пропс isActive из родительского элемента для передачи его в item
- При помощи request->routeIs($routeName)) проверяем соответствие входящего запроса "именованному маршруту"
- Метод request() содержит текущий HTTP запрос
- При помощи request()->path() можно проверить путь текущей страницы
- Метод routeIs($val) выполняет проверку совпадает ли текущий маршрут с заданным именем $val и возвращает treu || false
В данном уроке мы оптимизируем код вывода иконок соц. сетей при помощи отдельного массива
с названиями компании и её URL.
Данный урок был прослушан, но не повторен, потому как функцианал реализуемый лектором
я ввел заранее в рамках самостоятельной работы.
- В директории config создаем файл с названием проекта watch
- Внутри watch делаем return содержащий многоменрый массив с названием nav_items содержащий в себе пары ключ=>значение элементов навигации
- Убираем пропсы из кода. Теперь мы можем обращаться к элементам навигации через метод config('nav_items')
- Оборачиваем config(nav_items) в метод collect. Возможно будет целесообразно обернуть в коллекцию исходный массив определенный в директории config
- Повторяем тоже самое с иконками социальных сетей
- Прибиваем футер к низу экрана
- Верстка Tailwind. Наполняем index.blade.php контентом
- Определяем section в отдельный blade шаблон x-hero/li>
- Верстка структуры блока с последними курсами
- Вывод item курсов через перебор foreach(range(0, 3 as $i)) <- вернет массив элементов
- Выносим верстку в отдельный компонент
- Определяем маршрут course и задаем его нашим блокам курсов
- Задаем маршрут у карточек курсов
- Удаляем иконки из файла config
- Создаем файл x-icon в котором будут храниться наши иконки
- Внутри x-icon создаем ветвление @if ($name = "youtube")
- Что бы прокинуть эту переменную в x-icon пишем
- Создаем отдельный элемент для карточки курса
- Создаем массив данных с информацией о курсе
- Перебераем массив как $course и прокидываем эти данные пропсом в компонент курса
- Выносим массив с данными курса в отдельный файл (пока web) для доступа к нему из разных компонентов
- Определяем роут (index) get c return view('/,['courses' => $courses]); используем замыкание (use) для передачи функции доступа к массиву $courses
- Повторяем подобную маршрутизацию для courses для отображения внутри нее данных по курсам
- Для блока с курсами на главной странице и странице с курсами указываем уникальные h2 прокидывая атрибут () как строку
- Выводим "заглушку" курса (первый элемент массива $courses) по маршруту course
- Правим верстку
- В маршрутах, замыкания меняем на стрелочную функцию чтобы не использовать use, return и для более лаконичного вида
- У каждого элемента массива прописываем id
- В маршруте определяем course/{id}, Так же указываем $id в качестве аргумента к функции роута и в переданном ей массиве
- Прописываем id в роутах внутри ссылок навигационного меню
- Создаем таблицу для курсов php artisan make:migration create_courses_table
- За что отвечают методы up и down в миграциях
- Пишем структуру для таблицы с курсами (default,string,text,integer,nullable,)
- migrate:reset | migrate
- Заполняем таблицу произвольными данными
- В роутах подключаемся к БД и забираем из неё все данные select
- Используем функцию compact(). Создает массив, содержащий переменные и их значения.
- Изменяем обращения к элементам прошлого массива с курсами, на обращение к объектам (->) $course->title
- Считываем одну запись для страницы курса selestOne
- Переносим логику для обработки маршрутов в контроллеры
- Создаем контроллер php artisan make:controller CourseController
- Создаем экшены index и show и переносим в них соответствующие маршруты
- Указываем в качестве аргумента к show(string $id) для параметризированного роута
- Указываем тип возвращаемого значения для экшена show (: view)
- В маршруте указываем к какому экшену обратится роут для обработки запроса Route::get('/course/{id}', [CourseController::class, 'show'])->name('course');
- Включаем интеграцию с БД в phpstorm
Работа с БД через консоль на данный момент невозможна. Windows !
- В файле AppServiceProvider в методе boot определяем слушатель SQL запросов
- DB::listen(function(QueryExecuted $query)
- Обращаемся к методам $query dump([$query->sql, $query->bindings, $query->time]); внутри DB::listen
- Переписываем запрос на получение всех данных из таблицы courses с помощью SQL конструктора через метод table->get
- Переписываем запрос на получение данных из конкретного кортежа $course = DB::table('courses')->where('id', $id)->first();
- Рассматриваем сокращенный вариант записи через метод find($id)
- Инспектируем SQL запрос при помощи dump | $courses = DB::table('courses')->dump()->get();
- Метод ddRowSql() - позволяет отобразить SQL запрос с учетом переданных динамических данных
- query->toRawSql в AppServiceProvider
- Для доступа метода toRawSql обновляем зависимости composer
- Оборачиваем схожие маршруты в Route::group
- Указываем контроллер, который отвечает за обработку сгруппированных маршрутов
- Используем метод prefix('courses') для сокращения длинны маршрута
- Именуем маршруты по конвенции Laravel name('courses.index')
- Используем метод name и удаляем префикс из названия маршрута name('index')
- Меняем названия маршрутов (courses.show и courses.index) в ссылках ведущих на страницы курсов
- Меняем путь до страницы Courses в файле конфигурации
- Создаем новую директорию для хранения шаблонов страниц | pages
- Меняем наименование компонентов на show и index
- Переопределяем маршрут для view с учетом новой директории (pages)
- invokable controller - контроллер с одним методом
- Создаем контроллер php artisan make:controller --invokable
- Переносим содержимое роута главной страницы в созданный контроллер
- Импортируем класс DB
- Указываем инвокабл контроллер в роуте
- Создаем модель
- Используем модель в контроллере курса и Index контроллере
- Связываем модель с маршрутом при помощи параметризированного роута. Неявное связывание модели с параметром маршрута
- Указываем явно к какому полю в БД обращаться роуту для вывода данных /{course:lessons_count}
- Рассматриваем getRouteKeyName для глобального обращения роутов к определенным колонкам в БД
- Используем метод missing для отображения кастомных данный в случае если запись не будет найдена в БД
- Используем класс Redirect
- Создаем класс сеятель для создания моковых данных курсов
- В методе run Определяем класс Course с методом create внутри которого вкладываем массив с данными-заглушками
- Тестируем сидер php artisan db:seed --class=CourseSeeder
- В сидере определяем коллекцию с диапазоном значений (1, 10) для добавления 10 записей collect()->range(1, 10)->each(function (int $i)
- Указываем переменную $i в поле 'title' => 'Course'.$i, для нумерации курсов
- Course::truncate - метод для удаления данных из таблицы
- Указываем наш класс сидера в DatabaseSeeder и выполняем db:seed (без указания класса сидера) для заполнения БД мок данными
- Рассматриваем команду php artisan migrate:fresh для обновления миграций. флаг --seed дополнительно прокинет наши сиды
- Создаем класс фабрики CourseFactory
- Вызываем класс фабрики в CourseSeeder | Course::factory(10)->create(); в качестве аргумента factory() указываем кол-во создаваемых данных
- Указываем в модели трейт use HasFactory;
- Выполняем сидер
- Генерируем данные при помощи faker
- $this->faker->words(rand(2, 4), true)
- $this->faker->text()
- $this->faker->numberBetween(1, 50)
- Используем faker с интерполяцией blade
- Правка верстки
- Альтернативный синтаксис библиотеки fakerphp | глобальная функция fake()
- Оборачиваем title в ucfirst - Верхний регистр первого символа строки
- Выносим функции фейк данных в отдельную переменную для большей читаемости
- fake()->paragraph(1)
- generate helper code
- Добавляем массив с предопределенными данными для использования метода sequence в сидере
- В методе factory получаем кол-во элементов массива $data (массив с предопред. данными)
- Используем метод sequence и оператор спред (...)
- Course::paginate(); По умолчанию пагинация отображает 15 записей
- В конфигурационном файле tailwind указываем путь до стилей к пагинации
- darkMode: 'selector'
- Указываем методу paginate(4) в качестве аргумента кол-во элементов для вывода на одной странице
- На {{ $courses->links() }} вешаем проверку на существование метода, для отображения пагинации только на странице курсов method_exists
- В контроллере IndexController указываем $courses=Course::take(4)->get() для отображения 4 карточек курсов
- В инвокабл контроллере добавляем метод latest для сортировки курсов
- Метод oldest - сортировка в порядке возрастания
- Метод сортировки orderBy() - Сортировка по определенному значению указанному в аргементе метода
- Отвязываем логику компонента с курсами от логики курсов на главной странице (Правим шаблон с пагинацией)
- Создаем компонент поисковой строки
- Определяем иконку поиска в компонент x-icon
- Прописываем стили для формы поиска
- Установка tailwindCSS forms
- Правка стилей
- Рефакторинг верстки страницы с курсом
- В шаблоне курса выводим информацию о последнем обновлении {{ $course->updated_at->format('d.m.Y') }}
- Верстка блока со списком уроков. Использование спец символа ·
- Вывод списка уроков через foreach
- Рефакторинг. Разбиение страницы с курсом на компоненты
- Создаем модель Lesson, а так же фабрику, сидер и миграции
- Определяем необходимые нам поля в миграциях
- Заполняем фабрику и сидер
- Работа с функцией mt_rand() (в фабрике)
- Добавляем класс LessonSeeder в массив вызовов внутри DatabaseSeeder
- Запускаем миграцию
- Прокидываем данные из модели Lesson в компонент курсаю Работа с методом take()
- Указываем данные полученные из класса Lesson и раскидываем их в компоненте {{$lessons->title}} и т.д.
- Добавляем в миграции lessons, колонку с id (course_id) для связывания таблиц через внешние ключи (foreign key)
- Работа с методом foreign, а также references для связывания course_id (lessons) и id->on('courses')
- Работа с методом foreignId('course_id')->constrained(table: 'courses');
- Метод onUpdate('cascade') при обновлении кортежа обновит так же связанный с ним кортеж
- Метод onDelete('cascade') при удалении кортежа так же удалит связанный с ним кортеж
- Метод foreignIdFor(Class::class) - альтернативный синтаксис для реализации связывания таблиц
- cascadeOnUpdate и cascadeOnDelete - работают вметсе с foreignIdFor(Class::class)
- Course::inRandomOrder->first()->id() - связывание одной записи с другой в хаотичном порядке
- Schema::withoutForeignKeyConstraints() - отключение проверок
- Определив public function lessons(){}:HasMany, которая вернет $this->hasMany(Lesson::class) мы связываем модель Course с моделью Lesson
- Проверяем полученные (связанные) данные через интерфейс командной строки tinker
- Обращаемся к свойству lessons ($course->lessons) в котором находятся связанные с id <-> course_id данные
- Помещаем внешнее свойство в компонент
- Используем метод $lessons->count() для подсчета кол-ва уроков в коллекции
- Если мы не следуем соглашениям именования, мы можем указать связь таблиц (в Models\Course) явно $this->hasMany(Lesson::class, 'course_id', 'id')
- Меняем логику генерации случайных данных в LessonSeeder для кол-ва уроков в курсах | Course::each. В логике указываем добавление данных в поле 'number'
- В модели Lesson описываем отношение "Многие к одному" belongsTo
- При помощи Lesson::factory()->for($course) в LessonSeeder указываем с какой моделью связать сгенерированные данные
- Выводим данные о кол-ве уроков в карточку курса
- Удаляем добавление фейковых данных в таблицу из factory lessons_count и саму колонку lessons_count из миграций
В уроках по отношениям осталось много магии. К данным урокам нужно вернуться и посмотреть их повторно
В LessonSeeder мы обращаемся к Course::each.
Перебираем курсы и для каждого из них генерируем определенное кол-во уроков
так же связываем текущие сгенерированные уроки с конкретным курсом и
добавляем в таблицу данных number курса.
- Создаем контроллер в котором создаем метод show
- Определяем маршрут для урока и указываем к какому контроллеру и экшену обращаться Route::get('lessons/{lesson}', [LessonController::class, 'show'])
- В методе show(Lesson $lesson) в (LessonController) возвращаем (return) страницу lesson.show.blade.php и передаем ей массив lesson, указав вторым аргументом compact('lesson')
- Указываем маршрут в ссылках внутри компонентов в тегах
- Создаем компонент lesson.show.blade.php. Верстка
- Добавляем iframe видео на страницу урока
- Убираем лишние атрибуты (height, width)
- В поле title указываем $lesson->title
- Верстка кнопок плеера. пред/след. урок.
- Добавление иконок
- В роуте для кнопки "Все курсы" указываем отношение урока к курсу {{ route('courses.show', $lesson->course) }}
- Добавление иконок
- Добавление иконок
- Отделение компонента плеера
- Правка контейнера в tailwind.config | Рефакторинг верстки
- Создаем массив данных приближенных к реальным в LessonSeeder
- Добавляем поле repository_uri в миграции courses
- Обновляем фабрику и сидер курсов указав в них данные поля repository_uri к заполнению
- Добавляем ссылку указанную в сидере
- Прокидываем данные repository_uri в компонент уроков
- Для миграции lessons так же создаем доп. атрибуты. video_url и commit_url
- Правим фабрики и сидер для lesson
- Добавляем ссылку для кнопки Start Watching route('lessons.show', $course->lessons->first())
- Для модели Course прописываем отношение HasOne с названием firtsLesson
- Работа с функцией ofMany('column', 'aggregate') | return $this->hasOne(Lesson::class)->ofMany('number', 'min');
- Рефакторим firstLesson return $this->lessons()->one()->ofMany('number', 'min');
- Меняем роут с route('lessons.show', $course->lessons->first()) на route('lessons.show', $course->firstLesson)
- Создаем метод аксессор protected function previous(): Attribute
Этот метод используется для создания кастомного аксессора с помощью фасада Attribute.
Он задает поведение для получения значения атрибута previous.
- Возвращаем вызов метода make у Attribute внутри которого при помощи get определяем функцию
,которая вернет нам номер урока, который отличается от текущего на 1
- Обращаемся к course, lessons и firstWhere для поиска number курса -1
- Определяем роут в котором указываем наш метод аксессор {{ route('lessons.show', $lesson->previous) }}
- Отображаем кнопку с маршрутом, только в случае если есть урок с номером ниже текущего
- Пишем такую же реализацию для кнопки next