Наименование Ресурсно-компонентное программирование
Автор А.В.Михалькевич
Специальность
Ресурсно-компонентное программирование - это особый архитекутрный шаблон проектирования, используемый при разработке web-приложений с чётким разделением приложения на бэкенд и фронтенд. Ресурсы - это ответы бэкенда в формате JSON. Фронтенд, в свою очередь, взаимодействует с бэкендом только через компоненты. Отсюда и название - ресурсно-компонентное программирование.
В публикации рассматриваются особенности разработки и использования ресурсов и ресурсных классов на примере бэкенд фрэймворка Laravel.
,Анотация
Разработка фронтенд с помощью Vue.js и бэкенда с помощю Laravel. Закрытие и открытые маршруты. Взаимодействие фроентенд и бэкенда.
Anotation in English
Development of frontend using vue.js and backend with help Laravel. Protected and open routes. Interaction of frientend and backend.
Ключевые слова laravel, vue, routes, mvc, mvc, hmvc, oop, ООП
Количество символов 102477
Если в объектно-ориентированном программировании главным структурным элементом является объект, то в компонетно-ресурсном - компонент и ресуср. Компоненты формируются на стороне фронтенд, ресурсы - на строне бэкенд.
Например, такие фрэймворки как Laravel позволят разрабатывать целостное web-приложение (без разделения на бэкенд и фронтенд части), тогда используется архитектурный шаблон проектирвания HMVC (Hierarchical model–view–controller).
Но для разработки бэкендов такой шаблон проектирования не подходит, т.к. у бэкендов отсутствует V (view) из HMVC. Вместо шаблона бэкенд отдаёт ответ ввиде ресуров (Resource) с ответами в формате JSON. Laravel позволяет реализовывать такие ответы либо с помощью специальных методов (response(), json(), toArray() и др), либо с помощью специальных ресурсных классов.
Схематично и упрощённо такой подход в проектировании web-приложений можно представить следующим образом:
Так в ресурсно-компонентном программировании разрабатываются сразу два приложения: фронтенд и бэкенд. Фронтенд отправляет запрос пользователя в свой маршрутизатор, который в свою очередь перенаправляет запрос в маршрутизатор Laravel, который в зависимости от запроса вызывает тот или иной контроллер. В контроллере реализуется вся бэкенд логика, в том числе и обращение к модели (или к другим вспомагательным классам). Получив и преобразовав данные, контроллер их перенаправляет в ресурс, который возвращается в фронтенд.
Как в промышленности, так и в программировании, ресурсы - это то, что мы используем для производства. Модели можно представить ввиде полезных ископаемых. Тогда задача контроллера - это преобразование полезных ископаемых из недр моделей (данных) в ресурсы, которые могут быть использованы клиентом.
Со стороны фронтенда всё выглядит просто - запрос на бэкенд возвращает овет в формате JSON, который необходимо преобразовать в HTML. Используется ли на бэкенде сложная логика получения данных, или идёт обращение к JSON-файлу, фронтенду должно быть всё-равно, лишь бы быстро получить ответ с нужными данными.
В современных фронтенд-фрэймворках такие запросы на бэкенд и обработку ответов удобно реализовывать в компонентах. Тогда, компонент является инициатором запроса на бэкенд и получает ответы бэкенда.
В ресурсно-компонентном программировании жизненный цикл запроса проходит через две основные стадии: frontend и backend. Компоненты фронтенда получают ответы бэкенда через ресурсы.
Схематично жизненный цикл запроса можно представить следующим образом:
Ресурсно-компонентное программирование больше подходит для командной разработки средних и крупных проектов. Команда должна состоять из бэкендера, фронтендера, дизайнера и тестировщика. 4 человека + тимлид это уже команда. Если в команде больше людей, то несколько человек могут заниматься бэкендом, несколько фронтендом и т.д.
Ресурсы - это преобразованные данные предназначенные для клиента.
Ресурсы формируются на сторне бэкенда, и должны формироваться в рамках оговорённых стандартов. Общепринятым форматом ресурсов является формат Json.
На низком уровне, компоненты представляют собой изолированные части (блоки) пользовательского интерфейса (UI). В отдельные компоненты выделяются:
- большие обособленные структуры html, css и javascript (примеры: модальное окно, галерея);
- часто повторяемые структуры html, используемые с разными данными (примеры: статья, товар).
- элементы форм и схожих структур.
Компонент может взаимодействовать с другими компонентами.
Возьмем, к примеру, html-элемент. Мы можем использовать этот элемент с любыми технологиями в браузере, и мы можем передавать такие свойства, как width и height и слушайте такие события, как onclick.
На высоком уровне, мы можем сказать, что компоненты — это набор API-интерфейсов веб-платформы. (интерфейсы прикладного программирования), которые позволяют нам создавать теги HTML, со встроенными стилями и JavaScript логикой, которые будут работать в современных веб-браузерах и может использоваться фрэймворками JavaScript (React, Angular, Vue.js и т. д.).
Ожидается, что компоненты будут использоваться в веб-браузерах в течение длительного времени и предлагают множество преимущества, в том числе следующие:
• Веб-компоненты можно использовать повторно и работать между платформами.
• Веб-компоненты могут работать во всех основных веб-браузерах.
• Веб-компоненты просты в обслуживании и готовы к будущем, если они основаны на спецификациях веб-платформы.
Каждый компонент, не зависимо от того, к какому типу он относится обладают одним интерфейсом общения со всей системой. Однако, по способу подключения, можно выделить следующие типы компонентов:
- базовый (App.vue). Это главный компонент приложения, содержащий базовый шаблон или структуру приложения.
- маршрутные (это компоненты, вызываемые маршрутизатором, папка routes)
- вспомогательные компоненты.
Современные фронтенд-фрэймворки позволяют разделить приложения на компоненты. Так, проектирование программных приложений идёт путем построения независимых компонентов. Каждый компонент имеет интерфейс общения с остальной частью системы. Такую компонентную разработку нам предлагают фрэймворки Angular, React и Vue, Nuxt.
Сперва необходимо установить node.js
Информация по установке - https://nodejs.org/en/download
В Ubuntu node.js можно установить с помощью репозитория apt
:
Убедтиться в том, что node установлен, можно с помощью этой команды
node -v
Так же давайте проверим менеджер пакетов для node - npm
npm -v
Npm устанавливается следующим образом:
sudo apt install npm
Npx - еще один, альтернативный менеджер пакетов для Node. Проверяем версию также:
npx -v
Создать фронтенд можно множеством способов, нужно лишь выбрать фрэймворк или шаблон проектирования. Большинство из способов предполагает первоначальную установку фрэймворка. Фрэймворк предоставляет структуру проекта, под которую разработчику нужно подстроиться, изучить фрэймворк и ту структуру приложения.
Начнём разработку с файла package.json.
Создаем папку для будущего проекта, в ней файл package.json
со следующим содержимым:
{ "name": "nuxt-app", "scripts": { "dev": "nuxt" } }
Список всех исполняемых скриптов и зависимостей приложения хранятся в этом файле.
Для установки всех зависимостей воспользуемся командой
npm i
Теперь, чтобы запустить проект, необходимо запустить следующую команду:
npx naxi dev
Теперь главная страница сайта доступна по порту 3000
Основные папки фронтенда: components, assets, static
. Создать их можно одной командой:
mkdir components assets static
Также в корне проекта необходимо создать файл nuxt.config.js
:
touch nuxt.config.js
Рассмотрим начальную структуру проекта. Подробнее о каждой папке.
Дирректория pages
содержит маршрутные компоненты. Каждому файлу с расширением .vue этой папки Nuxt формирует соответствующий маршрут.
Маршруту главной страницы соответствует компонент index.vue. Все маршрутные компоненты создаём в этой папке. Например: about.vue
, contacts.vue
...
Ссылку на такой маршрут лучше формировать с помощью специального в тэга NuxtLink
.
Дирректория components
- папка для хранения маршрутных компонентов Vue.js.
Здесь мы можем создавать свои компоненты и импортировать файлы .vue.
Данный каталог содержит нескомпилированные активы, такие как файлы Stylus или Sass, изображения или шрифты. В template
ссылка на изображение может выглядеть так:
src="~/assets/your_image.png"
Подробнее - по ссылке Документация Nuxt
Автоматическое подключение плагинов из этой папки в проект. Сперва необходимо создать плагин в этой папке. Затем подключить его в файле nuxt.config.js
.
Для реализации запросов на бэкенд, можно воспользоваться Axios.
Установка модуля:
npm install @nuxtjs/axios
После чего, создайте файл axios.js со следующим содержимым:
import axios from 'axios'; //import {store} from '@/store/index'; export default defineNuxtPlugin(nuxtApp => { //console.log(store.state.token); axios.defaults.baseURL = 'http://localhost:8000/api/'; axios.defaults.headers["content-type"] = "application/json"; axios.defaults.headers.common.authorization = `Bearer `; axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*'; });
Подключение плагинов в файле nuxt.config.js
export default { css: ['~/assets/css/main.css'], modules: ['@nuxtjs/axios'], plugins: ['~/plugins/axios.js'] }
Для запросов на бэкенд используем axios. Авторизация через заголовок Bearer + токен.
При обработке данных, получаемых по ссылке есть несколько особенностей: необходимо выполнить запрос на бэкенд, а для выполнения запроса лучше использовать асинхронную функцию. Для выполнения запросов есть инструмент axios
.
npm install axios
https://github.com/axios/axios - По этой ссылке официальная документация
Функции, в которых будем делать запросы на бэкенд лучше делать асинхронными, и можем помещать их в специальное свойство экспорта methods
import axios from "axios"; export default { name: 'User', data() { return { users: [], }; }, methods: { async load() { const { data } = await axios.get(`https://randomuser.me/api/?results=6`); this.users = data.results; }, }, beforeMount() { this.load(); }, }
После того, как переменная users будет сгенерирована, в тэге templates с ней можно работать как с обычными массивами (если возвращается массив, с использованием атрибута v-for) или выводить значения через объект.
Можем усовершенствовать функцию load(), добавив входящий параметр. Так же обратите внимание, что мы можем не использовать beforeMount, а вызывать её непосредственно, в методе data():
import axios from "axios"; export default { name: 'User', data() { this.load(6); return { users: null, }; }, methods: { async load(number) { const data = await axios.get(`https://randomuser.me/api/?results=${number}`); this.users = data.data.results; }, }, }
Глобальные настройки для axios можем хранить в корневом файле main.js
import axios from 'axios'; ... axios.defaults.baseURL = 'http://localhost:8000/api/'; axios.defaults.headers["content-type"] = "application/json"; axios.defaults.headers.common.authorization = `Bearer ${localStorage.getItem('token')}`; axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
C 2021 года синтаксис script setup
стал рекомендуемым способом создания нового проекта на Vue.
Новый Composition API создает много удобных способов переиспользования кода в компонентах. Вспомним, что во Vue 2 логика разделялась по опциям: data, methods, props, created, и так далее:
export default { name: "FormSelect", data(){ return {SelectedValue:''} }, props: { options: { type: Array, default: () => [] } }, methods: { changeOptions(){ this.$emit('update:modelValue', this.SelectedValue) } } }
С Composition API мы не ограничены этой структурой и не обязаны разделять код по опциям. Пример компоноента AuthLogin:
import {ref} from "vue"; import axios from "axios"; export default { name: "AuthLogin", setup(){ const email = ref(''); const password = ref(''); const submit = async () => { const user_data = { 'email' : email.value, 'password' : password.value, }; const response = await axios.post('login', user_data); await localStorage.setItem('token', response.data.token); //console.log(response.data.user.name, response.data.token); document.location.href = '/home'; } return { email, password, submit } } }
Vue 3.2 ввел новый синтаксис <script setup>
Разработаем компонент видео плеера используя такой синтаксис.
Содержимое тега template:
Стили, в тэге style:
#player { width: 740px; margin: 20px auto; padding: 10px 5px 5px 5px; background: black; border: solid 1px rgba(134, 201, 148, 0.37); border-radius: 3px; } #play, #mute { padding: 2px 10px; width: 65px; border:solid 1px grey; font-weight:bold; border-radius: 3px; } #buttons { float:left; height: 20px; } #bar { float:left; width: 400px; height: 20px; padding: 1px; margin: 2px 5px; border:solid 1px darkgreen; background: white; } #progress { width: 0; height: 18px; background: rebeccapurple; } .clear { clear: both; }
А вот и сам скрипт, который должен находится в тэге <script setup>:
import {ref, onMounted} from "vue"; const media = ref(); const maxim = ref(400); const play = ref(); const progress = ref(); const bar = ref(); const loop = ref(); const volume = ref(); const mute = ref(); onMounted(async ()=>{ media.value = document.getElementById('media'); progress.value = document.getElementById('progress'); mute.value = document.getElementById('mute'); play.value = document.getElementById('play'); bar.value = document.getElementById('bar'); }); const push = () => { if(!media.value.paused && !media.value.ended){ media.value.pause(); play.value.value = 'Play'; clearInterval(loop); }else{ media.value.play(); play.value.value = 'Pause'; loop.value = setInterval(status, 1000); } }; const status = () =>{ if(!media.value.ended) { var size = parseInt(media.value.currentTime * maxim.value / media.value.duration); progress.value.style.width = size + 'px'; }else{ progress.value.style.width = 0; clearInterval(this.loop); play.value.value = 'Play' } }; const sound = () => { if(mute.value.value == 'Mute'){ media.value.muted = true; mute.value.value = 'Sound'; }else{ media.value.muted = false; mute.value.value = 'Mute'; } }; const level = () => { media.value.volume = volume.value.value; }
В результате получим компонент с таким плэеером:
Компоненты это переиспользуемые экземпляры Vue. Компоненты принимают такие опции
, как как data
, computed
, watch
, methods
, хуки жизненного цикла и их можно переиспользовать множество раз. Например, у вас могут быть компоненты для заголовка, боковой панели, контента, каждый из которых может содержать другие компоненты для ссылок, постов блога...
Vue позволяет нам создавать интерфейсные веб-приложения с компонентами. С ними мы можем разделить наше приложение на маленькие, повторно используемые части, которые составляются вместе, чтобы сделать большое приложение. Этот композиция выполняется путем вложения. Чтобы разные части приложения составлялись вместе, мы может передавать данные между ними. Компоненты можно брать из библиотек, а также можем создавать сами.
Компоненты - это файлы с расширением vue, которые обычно хранятся в папке /src/components
. Компоненты могут принимать значения из родительских компонетов и могут быть использованы для передачи данных в дочерние компоненты.
После установки router-view появляется такой тип компонента, как - маршрутный. Маршрутный компонент - это компонент ленивой подгрузки в тэг router-view. Вспомогательный компонент - все остальные компоненты шаблона.
Компонент состоит из:
- тэга template
, внутри которого содержится html-код и vue-тэги других компонентов.
- скрипты, содержащиеся в тэге script
- стили в тэге style
Первоначальной задачей после установки фронтенда, необходимио разбитие шаблона на компоненты. Но, сперва визуально разделим страницу шаблона на две части - меняющуюся часть и неизменную. Часто, к неизменной относится меню, шапка и подвал сайта. Так же, часто элементы меню лучше переносить в отдельный вспомогательный компонент Menu.vue
В компонент переменные могут поступать разными способами. Но, не зависимо от способа поступления переменной и её типа, для вывода их значений в тэге template можно воспользоваться диррективой {{}}.
Вывод переменной строки или числа:
{{user_name}}
Вывод переменной объекта
{{user.name}}
Вывод массива
Hello, {{ user.name }}, from {{ user.location.city }}!
Обратите внимание, что если нужно добавить значение переменной в атрибут к какому-нибудь тэгу, для этого можно воспользоваться диррективой :
.
:key="user.id"
Импортировать компонент можно из любого другого компонента. Например, импорт вспомогательных компонентов в базовом компоненте App.vue:
import MenuTop from "@/components/templates/MenuTop"; import BootstrapFooter from "@/components/templates/BootstrapFooter"; export default { components:{ MenuTop, BootstrapFooter } }
Глобальная регистрация компонентов в файле main.js
import components from '@/components/ui'; const app = createApp(App); components.forEach(component => { app.component(component.name, component); }) app.use(router).mount('#app');
Переменные для компонентов можем создавать в тэге script,
Для создания переменных мы можем использовать метод data
:
export default { data() { return { name: "test", } } }
Тогда в template мы можем вывести значение:
Hello, {{ name }}!
Vue3
Во Vue3 для создания переменных можно воспользоваться модулем ref
. Пример:
import {ref, onMounted} from "vue"; import axios from 'axios'; export default { name: 'About', setup() { const users = ref([]); onMounted(async () => { const response = await axios.get('https://randomuser.me/api/?results=6'); users.value = response.data.results; console.log(users.value); }); return { users } } }
Обратите внимание на то, что после объявления переменной users, мы можем к ней обращаться с помощью users.value
, например для того чтобы определить или переопределить её значение.
Такой способ создания и работы с переменными (с помощью модуля ref и в диррективе setup используется в Composition API)
Из часто используемых элементов лучше сделать библиотеку. Поэтому рассмотрим, как разработать свою библиотеку компонентов, куда будут входить элементы форм и диалоговое окно.
Библиотечные компоненты будем хранить в папке ui, дирректории components
Сперва в файле MyDialog заполним template
Диалоговое окно состоит из двух элементов: элемента с классом dialog
и элемент с классом dialog_content
. dialog
- это одновременно и фон для диалогового окна, и элемент содержащий контент диалогового окна. Отображение или скрытие этого элемента зависит от переменной show
. А по клику на фон, будет вызываться функция hideDialog
, которая закроет окно. Событие клик не должно распространяться на контентную часть диалогового окна, поэтому в элементе dialog_content
добавим @click.stop
.
Добавим скрипт и стили диалогового окна.
Script
export default { name: "MyDialog", props: { show: { type: Boolean, default: false } }, methods:{ hideDialog(){ this.$emit('update:show', false); } } }
Style
.dialog { top: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.5); position: fixed; display: flex; height: 100%; } .dialog_content { margin: auto; background: white; border-radius: 12px; min-height: 50px; min-width: 500px; padding: 20px; }
Сперва заполним template
Script:
export default { name: "FormInput", props:['modelValue', 'typeInput'], methods:{ updateInput(event){ this.$emit('update:modelValue', event.target.value) } } }
Template:
Script
export default { name: "FormSelect", data(){ return { SelectedValue:'' } }, props: { options: { type: Array, default: () => [] } }, methods: { changeOptions() { this.$emit('update:modelValue', this.SelectedValue); } } }
Template
Script
export default { name: "FormButton", props:['nameValue'], }
Библиотечные компоненты мы можем сделать глобальными. Тогда при использовании их не нужно будет импортировать. Для этого в папке ui создадим файл index.js со следующим содержимым:
import MyDialog from "@/components/ui/elements/MyDialog"; import FormInput from "@/components/ui/forms/FormInput"; import FormSelect from "@/components/ui/forms/FormSelect"; import FormButton from "@/components/ui/forms/FormButton"; export default [ MyDialog, FormInput, FormSelect, FormButton ];
Т.е. в этом файле подключим все библиотечные компоненты.
Далее немного перепишем файл корневой файл main.js
... import components from '@/components/ui'; ... const app = createApp(App); components.forEach(component => { app.component(component.name, component); }) app.use(router).mount('#app');
В этом файле мы подключим index.js из нашей библиотеки. Потом создадим объект app - объект приложения. И с помощью цикла forEach пройдёмся по всем библиотечным компонентам, добавляя их к приложению.
Теперь в любом компоненте приложения мы можем использовать библиотечные компоненты без импортирования. Например так:
Script в Composition API.
import axios from "axios"; import {onMounted, ref, watch} from "vue"; export default { name: 'ProductsView', setup() { const catalogs = ref([]); const DialogVisible = ref(false); const SelectedOption = ref(''); const ProductName = ref(''); const ProductDescription = ref(''); const DialogShow = () => { DialogVisible.value = true; }; const SendData = () => { console.log(ProductName, ProductDescription, SelectedOption); }; onMounted(async () => { const response = await axios.get('catalogs'); catalogs.value = response.data; console.log(catalogs.value); }); watch(SelectedOption, (newValue) => { console.log(newValue); }); return { catalogs, DialogVisible, SelectedOption, ProductName, ProductDescription, DialogShow, SendData } }, }
Для разработки бэкенд API воспользуемся фрэймворком Laravel. Но т.к., за отображение элементов страниц сайта отвечает фронтенд, то основной задачей Laravel будет формирование ресурсов, к которым будет обращаться фронт за данными.
Со стороны фронтенда, на бэкенд наккладываются следующе требования
1. на каждый запрос фронтенда (request) бэкенд должен отдать ответ (respnse) в формате Json! никаких перенаправлений (redirect) и шаблонов (blade)
2. никаких лишних данных: например, если фронтенд запрашивает email и name пользователя, то в идеале, в ответе должны быть только эти данные.
3. скорость: чем быстрее тем лучше.
4. формат данных: используем разные типы запросов для разных действий. Get- вывод данных, Post - добавление данных, Put -обновление данных и Delete - удаление данных.
5. тестирование: рабочие маршруты на бэкенд не только должны быть протестированы, но и добавлены в Postmen
Официальный сайт laravel – http://laravel.com
Перед установкой убедитесь в том, что у вас уже установлены:
- PHP актуальной версии (в некоторых операционных системах может понадобиться ручная установка следующих модулей: Mcrypt PHP Extension OpenSSL PHP Extension MbstringPHPExtension;
- база данных (MySQL);
- менеджер зависимостей Composer.
Также перед установкой необходимо убедиться в наличии менеджера зависимостей composer и обновить его с помощью следующей команды:
composer self-update
Установка Laravel
Сперва переходим в корневую папку сервера. Для Windows OpenServer - эта папка domains:
cd domains
Для Linux - это папка www
.
Для Mac - папка htdocs
:
После чего запускаем команду установки:
composer create-project laravel/laravel
Таким образом в текущей папке появится новая папка laravel с файлами проекта. В дальнейшем её можно переименовать. Однако, если есть необходимость сразу установить фрэймворк в папку с оределенным именем, можно воспользоваться другой командой:
Composer create-project laravel/laravel your-project-name
Если вы используете сервер Apache, то в корне проекта создайте файл без имени с расширением .htaccess и со следующим содержимым:
RewriteEngine On RewriteRule ^(.*)$ public/$1 [L]
Если вы используете сервер Nginx, воспользуйтесь следующим конфигурационным файлом:
server { listen 80 default_server; listen [::]:80 default_server; # Log files for Debugging access_log /var/log/nginx/laravel-access.log; error_log /var/log/nginx/laravel-error.log; # Webroot Directory for Laravel project root /var/www/laravel/public; index index.php index.html index.htm; # Your Domain Name server_name localhost; location / { try_files $uri $uri/ /index.php?$query_string; } # PHP-FPM Configuration Nginx location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/run/php/php7.3-fpm.sock; fastcgi_index index.php; fastcgi_read_timeout 1500000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }
Таким образом мы перевели все запросы клиента в папку public и закрыли доступ ко всем остальным папкам проекта.
Однако, Laravel содержит свой встренный сервер, и мы можем запустить проект без Apache, Nginx или других сторонних серверов.
php artisan serve
Artisan - это интерфейс командной строки (CLI) входящий в состав Laravel. Он предоставляет ряд команд, которые будут полезны при разработке вашего приложения. Чтобы посмотреть список всех доступных Artisan-команд, вы можете воспользоваться командой list
:
php artsian listКаждая команда так же содержит "подсказку", которая отображает и описывает все возможные аргументы и опции доступные для команды. Чтобы увидеть подсказку, просто напишите перед названием команды слово help:
php artisan help migrate
С базой данных Laravel взаимодействует с помощью разных движков: query builder или Eloquent ORM. На данный момент Laravel поддерживает четыре системы баз данных: MySQL, PostgreSQL, SQLite и SQL Server
Настройки подключения к базе данных находятся в файле .env
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=6603 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=helloworld
Построитель запросов (Query builder) Laravel предлагает интерфейс для создания и выполнения запросов к базе данных. Его можно использовать для выполнения большинства операций с базой данных в вашем приложении и он отлично работает со всеми поддерживаемыми Laravel системами баз данных.
Вы можете использовать метод table
фасада DB
, чтобы начать запрос. Метод table
возвращает текущий экземпляр построителя запросов для данной таблицы, позволяя вам связать больше ограничений к запросу
$cat = DB::table(‘categories’);
Рассмотрим реализацию CRUD с помощью построителя запросов
SELECT
Для создания SELECT-запросов, сперва необходимо создать объект классаDB и статического метода table(), входящим параметром в который необходимо передать имя таблицы.
Для получения результата можно воспользоваться следующими методами:
Получить все поля таблицы можно, используя метод get().
$cat = DB::table(‘categories’)->get();
Уточнение запроса осуществляется с помощью метода where()
$cat = DB::table(‘categories’)->where(‘showhide’, ‘=’, ‘show’)->get();
Конструктор запросов может быть использован для выборки данных из нескольких таблиц через JOIN.
DB::table('users') ->join('contacts', 'users.id', '=', 'contacts.user_id') ->join('orders', 'users.id', '=', 'orders.user_id') ->select('users.id', 'contacts.phone', 'orders.price');
Если необходимо сделать сложную фильтацию, то в значение методов where()или orWhere() можно добавлять функуию с дополнительным query-запросом:
DB::table('users') ->where('name', '=', 'Джон') ->orWhere(function ($query) { $query->where('votes', '>', 100) ->where('title', '<>', 'Админ'); })->get();
INSERT
DB::table('users')->insert( array('email' => 'john@example.com', 'votes' => 0) );
Если необходимо вставить данные и при этом тут же получить id, можно воспользоваться методом insertGetId.
$id = DB::table('users')->insertGetId( array('email' => 'john@example.com', 'votes' => 0) );
Laravel также поддерживает множественную вставку данных:
DB::table('users')->insert(array( array('email' => 'taylor@example.com', 'votes' => 0), array('email' => 'dayle@example.com', 'votes' => 0), ));
UPDATE
Для обновления данных можно воспользоваться методом update()
.
DB::table('users')->where('id', 1) ->update(array('votes' => 1));
Однако, обновление можно осуществить и с помощью метода save()
$obj = DB::table('users')->where('id', 1); $obj->votes = 1; $obj->save();
DELETE
DB::table('users')->where('votes', '<', 100)->delete(); DB::update('update users set votes = 100 where name = ?', array('John'));
Выполнение запросов другого типа
DB::statement('drop table users');
Транзакции
Для выполнения запросов внутри одной транзакции, можно воспользоваться методом transaction.
DB::transaction(function(){ DB::table('users')->update(array('votes' => 1)); DB::table('posts')->delete(); });
Доступ к соединениям
При использовании нескольких подключений к БД, вы можете получить к ним доступ через метод DB::connection:
$users = DB::connection('foo')->select(...); $pdo = DB::connection()->getPdo(); // получение объектаPDO указанног соединения. DB::reconnect('foo'); // переподключение к базе данных
Если вам нужно отключиться от БД - например, чтобы не превысить лимит max_connections в БД, вы можете воспользоваться методом disconnect
DB::disconnect('foo');
По умолчанию, Laravel записывает все SQL-запросы в памяти, выполненные в рамках текущего HTTP-запроса.
Однако, в некоторых случаях, как, например, при вставке большого количества записей, это может быть слишком ресурсозатратно. Для отключения журнала вы можете использовать метод disableQueryLog:
DB::connection()->disableQueryLog();
Для получения массива выполненных запросов используйте метод getQueryLog:
$queries = DB::getQueryLog();
Миграции базы данных являются весьма полезны для любого проекта, особенно для проектов с несколькими разработчиками, позволяя иметь последнюю версию базы данных у всех разработчиков. В Laravel для этого достатчно выполнить одну команду в командной строке.
Для создания новой миграции нам понадобится интерфейс командной строки Laravel — «Artisan».
Итак, откроем консоль командной строки из папки, где расположен файл artisan. В консоли введем следующую команду:
php artisan make:migration create_categories
Консоль должна ответить нам следующей фразой:
“Great! New migration created!”
Если получили такой ответ, то перейдем в папку application/migrations. Там должен находится файл 2014_04_20_210359_create_users.php (к имени миграции artisan добавляет текущую дату). Откроем данный файл. Увидим следующее:
Class Create_Categories { /** * Внести изменения в базу данных. * @return void */ public function up() { // } public function down() { // } }
Класс миграции содержит два метода up() для внесения изменений в таблицу базы данных и down() для отмены действий метода up(). Например, если мы создаем таблицу в up(), то в down() ее нужно удалить.
Допишем действия up() и down()
public function up(){ Schema::create('categories', function ($table) { // auto incremental id (PK) $table->increments('id'); // varchar 32 $table->string('name', 32); // enum $table->enum('showhide', array('show','hide')); // created_at | updated_at DATETIME $table->timestamps(); }); } public function down(){ Schema::drop('categories'); }
Внутри функции мы можем использовать следующие красивые методы для определения структуры таблицы:
Перед тем как на основе существующих миграций создавать таблицы, давайте создадим таблицу migrations, в которой laravel будет хранить данные о самих миграциях:
// ВНИМАНИЕ!Данну команду необходимо выполнять, если у нас еще не создана таблица migrations php artisan migrate:install
В результате мы должны увидеть
“Migration table created successfully.”
Теперь, когда таблица создана, мы можем выполнить саму миграцию:
php artisan migrate
Чтобы удалить таблицу, можно выполнить команду rollback
php artisan migrate:rollback
Для загрузки первоначальных данных имеется artisan-команда
php artisan make:seeder UserTableSeeder
Класс Seeder содержит только один метод по умолчанию run()
.
use Illuminate\Database\Seeder; use Illuminate\Database\Eloquent\Model; class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('users')->insert([ 'name' => str_random(10), 'email' => str_random(10).'@gmail.com', 'password' => bcrypt('secret'), ]); } }
После того, как данные для загрузки подготовлены, нужно выполнить artisan-команду db:seed
php artisan db:seed// все классы php artisan db:seed --class=UserTableSeeder // только указанный класс
Откатить данные можно с помощью команды rolback
php artisan migrate:refresh--seed
Можно использовать вспомогательные классы, в которых будет прописана логика загруки данных, загружаются они с помощью метода call().
public function run(){ Model::unguard(); $this->call(UserTableSeeder::class); $this->call(PostsTableSeeder::class); $this->call(CommentsTableSeeder::class); }
Модель - это класс, ассоциированный с именем таблицы из базы данных. Причем, по-умолчанию, имя класса модели равняется имени таблицы из базы из которой убирается окончание “s”. Таким образом, модель Product ассоциирована с таблицей products.
Запросы в таблицу из модели формируются с помощью рассмотреннего ранее построителя запросов.
Для создания моделей у artisan имеется специальная команда.
php artisan make:model Flight
Если модель связывана с таблицей, а миграции создают таблицы, то логично было бы предположить, что имеется специальная комадна связывающая модель с миграцией. Действительно, у artisan имеется специальная команда, создающая сразу два файла: файл модели и файл миграции.
php artisan make:model Flight --migration //или php artisan make:model Flight -m
Не важно, какой командой мы воспользовались, в папке models проекта появится файл Flight.php со следующим содержимым:
namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model { // }
В отличии от контроллера, который без методов бесполезен, такой пустой класс модели, мы уже можем использовать. Через данный класс модели, мы можем использовать методы конструктора запросов.
В Laravel имеется встроенная модель User, которая нас связывает с таблице users (используемая в авторизации).
Рассмотрим основные CRUD-операции (Create, Read, Update и Delete).
Извлечение данных
Как только модель определена, у вас всё готово для того, чтобы можно было выбирать и создавать записи. Обратите внимание, что вам нужно создать в этой таблице поля updated_at и created_at. Если вы не хотите, чтобы они были автоматически используемы, установите свойство $timestamps класса модели в false.
Получение всез записей модели
$users = User::all();
Получение записи по первичному ключу:
$user = User::find(1); var_dump($user->name);
Метод find может принимать входящим параметром массив:
$user = User::find([1,2,4]);
Для извлечения данных по другим полям можно воспользоваться методом where
.
$user = User::where(‘email’,$email)->get();
При этом конечный метод get
возвращает массив объектов, для вывода которых в последствии необходимо использовать foreach.
Метод where() может использоваться с тремя входящими параметрами, тогда вторым входящим параметром передается ключевой символ =
, !=
, >
, <
, <>
или ключевое слово LIKE
.
$user = User::where(‘name’,’!=’,$name)->get()
Метод where можно использовать в запросе несколько раз подряд, тогда последующие where работают как AND WHERE.
Множественный вызов метода where:
$user = Tovar::where(‘cat_id’,1)->where(‘showhide’,’show’)->get()
Существует еще один полезный метод, связанный с подзапросом where – это whereIn. WhereIn вторым (или трейтим) входящим параметром принимает массив значений, по которым необходимо формировать запрос.
$models = Model::whereIn('id', [1, 2, 3])->get();
Все методы, доступные в конструкторе запросов, также доступны в запросах с использованием моделей.
Иногда вам нужно возбудить исключение, если определённое значение не найдено, что позволит вам его отловить в обработчике App::error() и вывести страницу 404 («Не найдено»).
$model = User::findOrFail(1); $model = User::where('votes', '>', 100)->firstOrFail();
Вставка
Данные через модель могут быть вставлены двумя способами:
Пример одиночной вставки:
$user = new User; // создаем объект $user->name = 'Джон'; // данные $user->save(); // сохранение
Пример множественной вставки:
User::create(['name'=>'Джон']);
Считается, что способ множественной вставки менее безопасен одиночной. Поэтому для реализации множественной вставки, нужно специальное разрешение модели. В модели нужно объявить свойство $fillable
, содержащее массив элементов полей, разрешенных для вставки.
$fillable = ['name'];
Обновление
Обновление данных по id:
$user = User::find(1); $user->email = 'alex@ya.ru'; $user->save();
Удаление
Удаление данных по id
$user = User::find(1); $user->delete();
Заготовки запросов
Заготовки позволяют повторно использовать логику запросов в моделях. Для создания заготовки просто начните имя метода со scope:
Создание заготовок запроса
class User extends Model { public function scopePopular($query) { return $query->where('votes', '>', 100); } public function scopeWomen($query) { return $query->whereGender('W'); } }
Использование заготовок запросов:
$users = User::popular()->women()->orderBy('created_at')->get();
Можно также использовать заготовки запросов с входящими параметрами:
class User extends Model { public function scopeOfType($query, $type) { return $query->whereType($type); } }
Вызов заготовок запросов с входящими параметрами:
$users = User::ofType('member')->get();
Таблицы базы данных часто связаны друг с другом. Например, пост в блоге может содержать много комментариев или заказ может быть связан с пользователем, который его разместил. Eloquent упрощает управление этими отношениями и работу с ними, а также поддерживает множество общих отношений:
Примеры связующих таблиц: страна - флаг, страна - столица, пользовать - аккаунт... Т.е. текущая модель может быть связана только с одной другой моделью.
Связь осуществляется с помощью метода hasOne()
... class User extends Model { public function account(){ return hasOne(Account::class); } }
Для вызова связи в классах или шаблонах приложения, достаточно обратиться к этому методу: $obj->account()
, или свойству $obj->account
, которые возвращают объект связующей модели.
BelongsToMany
Один пользователь может содержать множество телефонов. Тогда следует использовать связь hasMany(). Другими примерами данного отношения «один ко многим» является статья в блоге, которая имеет «много» комментариев или мама, имеющая детей. Мы можем смоделировать это отношение таким образом:
class User extends Model{ public function phones() { return $this->hasMany('Phone'); } }
Теперь мы можем получить все телефоны пользователя с помощью динамического свойства:
$phones = User::find(1)->phones;
В этом случае, мы получили массив телефонов, и для просмотра всех телефонов, нужно пройтись по элементам массива.
foreach($phones as $one){ echo $one->body; echo '\n'; }
Если нужно добавить ограничения на получаемые телефоны, можно вызвать метод phones() и продолжить добавлять условия:
$phone = User::find(1)->phones()->where('title', '=', 'foo')->first();
Теперь мы получили один телефон, который сразу можно вывести на экран:
$phone->body
Рассмотрим еще один пример, который выявит недостатки связи hasMany. Предположим, у нас есть модель Book и модель Author. Book принадлежит (связь belongsTo) Author. И наоборот, Author содержит (связь hasMany) множество Book.
Определение связи в модели Book:
class Book extends Model { public function author() { return $this->belongsTo('Author'); } }
Получить все названия книг можно следующим образом:
foreach (Book::all() as $book) { echo $book->name; }
Мы обошлись всего одним заросом. Даже если будет 100 книг, это всё-равно один запрос:
SELECT * FROM books
Однако, если к выводу названия книги добавим автора:
foreach (Book::all() as $book) { echo $book->name; echo $book->author->name; }
То, получаем 101 запрос. Первым запросом получаем все книги, а потом в цикле делаем запрос на получение автора книги.
SELECT * FROM books; SELECT * FROM authors WHERE id = 1; SELECT * FROM authors WHERE id = 2; SELECT * FROM authors WHERE id = 3; ...
Это очень не рационально. И чтобы сократить нагрузку на сервер бызы данных, можно воспользоваться активной загрузкой.
Активная загрузка
Акнивная загрузка подразумевает подключение связующей модели с помощью метода with
.
foreach (Book::with('author')->get() as $book) { echo $book->author->name; }
Таким образом будут выполнены всего два запроса:
SELECT * FROM books; SELECT * FROM authors WHERE id IN(1,2,3);
Конечно, вы можете загрузить несколько отношений одновременно:
$books = Book::with('author', 'publisher')->get();
Разумное использование активной загрузки поможет сильно повысить производительность приложения.
Для проведения теста иногда приходится заполнить базу данных, причём данные должны выглядеть более-менее как настоящие, а не просто набором букв. Данные можно вводить и в ручную, но это пойдёт, пока их мало. А в противном случае лучше воспользоваться специальным инструментом для заполнения тест данными — фабрикой моделей.
Хоть и при помощи сидов можно создать несколько записей, но фабрика моделей генерирует множество.
Файлы фабрик моделей хранятся в папке database/factories
, и там уже есть один готовый файл UserFactory.php
— фабрика для модели User
. В Laravel 8 вызываемый метод definition()
выглядит так:
public function definition() { return [ 'name' => $this->faker->name, 'email' => $this->faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; }
Из сидера фабрику можем вызвать так:
public function run() { \App\Models\User::factory(10)->create(); }
Далее запускаем сидер:
php artisan db:seed
Стоит так же отметить, что запускать фабрики мы можем не только из сидов, но и из других мест приложения. Например, из консоли. Для этого, сперва надо войти в tinker
:
php artisan tinker
После полученного приглашения, можно выполнить фабрику:
\App\Models\User::factory(10)->create();
При создании API может потребоваться слой преобразования, находящийся между моделями Eloquent и ответами JSON, которые возвращаются фронтенд части web-приложения. Например, бывает необходимо отображать определенные атрибуты только для некоторых пользователей, а не для всех, или, кроме атрибутов, отображать отношения моделей. Классы ресурсов Eloquent позволяют легко и выразительно преобразовывать модели и коллекции моделей в JSON.
Конечно, всегда можно преобразовать модели или коллекции Eloquent в JSON, используя их методы toJson
; однако ресурсы Eloquent обеспечивают более детальный и надежный контроль над выводом ответов.
Ресурсы расширяют класс Illuminate\Http\Resources\Json\JsonResource
. Чтобы сгенерировать новый ресурс, можно использовать artisan-команду make:resource
. Эта команда поместит новый класс ресурса в каталог app/Http/Resources
приложения:
php artisan make:resource UserResource
Класс ресурсов представляет собой единую модель, которую необходимо преобразовать в структуру JSON. Например, вот простой класс ресурса UserResource
:
namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class UserResource extends JsonResource { /** * Преобразовать ресурс в массив. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } }
Каждый класс ресурсов определяет метод toArray
, возвращающий массив атрибутов, которые должны быть преобразованы в JSON, когда ресурс возвращается в качестве ответа из метода маршрута или контроллера.
Мы можем получить доступ к свойствам модели непосредственно из переменной $this
. Это связано с тем, что класс ресурсов автоматически возвращает свойства и методы к базовой модели. Как только ресурс определен, он может быть возвращен из маршрута или контроллера. Ресурс принимает основной экземпляр модели через свой конструктор:
use App\Http\Resources\UserResource; use App\Models\User; Route::get('/user/{id}', function ($id) { return new UserResource(User::findOrFail($id)); });
Чтобы вернуть коллекцию ресурса или ответ с постраничной разбивкой, то необходимо использовать метод collection
класса ресурса, при создании экземпляра ресурса в маршруте или контроллере. Пример:
use App\Http\Resources\UserResource; use App\Models\User; Route::get('/users', function () { return UserResource::collection(User::all()); });
В Laravel, чтобы отправить клиенту данные в формате JSON можно любым из перечисленных способов:
Из экшна (метода) контроллера вернуть результат запроса с методом toJson(<параметры кодирования>)
Этот метод можно вызвать как для коллекции объектов модели:
public function index(){ return User::all()->toJson(JSON_UNESCAPED_UNICODE); }
Так и для отдельного объекта модели:
public function show(User $user){ return $user->toJson(JSON_UNESCAPED_UNICODE); }
При вызове метода toJson() можно указать параметры кодирования. Так, использованная в приведенных примерах опция кодированая JSON_UNESCAPED_UNICODE указывает не преобазовывать многобайтные символы (например, символы кириллицы) в их коды.
Вернуть из экшна контроллера результат вызова метода toArray()
. Он полностью аналогичен методу toJson()
, но без параметров кодирования
// для отдельного объекта модели return $user->toArray(); // для коллекции объектов модели return User::all()->toArray();
Если не требуется извлекать связные записи, то можно использовать метод attributesToArray()
return $user->attributesToArray();
Вернуть из экшна контроллера результат приведения объекта модели или коллекции к строковому типу
return (string) User::all();
Вернуть из эшкна контроллера объект модели или коллекции:
public function show(User $user){ return $user }
Если необходимо отправить клиенту закодированные в формат JSON произвольные данные, то можно воспользоваться инструментом json()
класса ResponseFactory
:
public function show(User $user){ return response()->json($user); }
Наконец, для реализации более сложных ответов, можно воспользоваться ресурсными классами. Далее сосредоточимся на ответах с помощью ресурсных классов.
Помимо создания ресурсов, преобразующих отдельные модели, имеется возможность создавать ресурсы, отвечающие за преобразование коллекций моделей. Это позволяет ответам JSON включать ссылки и другую метаинформацию, имеющую отношение ко всей коллекции конкретного ресурса.
Чтобы сгенерировать новую коллекцию ресурса, нужно использовать флаг --collection
при создании ресурса, или включить слово Collection
в имя ресурса при его создании. Это укажет Laravel, что он должен создать коллекцию ресурса. Коллекции ресурса расширяют класс Illuminate\Http\Resources\Json\ResourceCollection
:
php artisan make:resource User --collection php artisan make:resource UserCollection
После создания класса коллекции ресурса, можно легко определить любые метаданные, которые должны быть включены в ответ:
namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class UserCollection extends ResourceCollection { /** * Преобразовать коллекцию ресурса в массив. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; } }
После определения коллекции ресурса, ее можно вернуть из маршрута или контроллера:
use App\Http\Resources\UserCollection; use App\Models\User; Route::get('/users', function () { return new UserCollection(User::all()); });
В JSON-объекты можно заносить:
public function toArray() { return [ 'key' => $this->id, 'title'=> $this->name, ... ]; }
return [ ..., 'product_counts' => $this->products()->count() ];
return [ ..., 'powered_by' => 'Laravel' ];
Для вычислений по условию можно воспользоваться специальными ресурсными методами: when
, mergeWhen
:
return [ 'count' => $this->when($this->products()->exists(), $this->products()->count()), 'secret' => $this->when(Auth::user()->isAdmin(), 'secret-value'), ];
Вместо выводимого значения (второй входящий параметр метода when
), можно использовать анонимную функцию:
return [ ..., 'count' => $this->when($this->products()->exists(), function(){ return $this->products()->count(); }) ];Если есть несколько атрибутов, которые должны быть включены в ответ ресурса только на основе одного и того же условия, то можно использовать метод
mergeWhen
для включения атрибутов в ответ только тогда, когда данное условие истинно:
public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, $this->mergeWhen(Auth::user()->isAdmin(), [ 'first-secret' => 'value', 'second-secret' => 'value', ]), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }
return [ ... 'parent' => new ProductResource($this->parent) ];
return [ ... 'products' => new ProductResourceCollection($this->products) ];
use App\Http\Resources\PostResource; /** * Transform the resource into an array. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts' => PostResource::collection($this->posts), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }В том числе в связях "многие-со-многими":
return [ ... 'products' => $this->pivot->products ];
whenLoaded
use App\Http\Resources\PostResource; /** * Transform the resource into an array. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'posts' => PostResource::collection($this->whenLoaded('posts')), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }В дополнение к условному включению сведений об отношениях в ответы вашего ресурса вы можете условно включить данные из промежуточных таблиц отношений многие ко многим, используя метод whenPivotLoaded. Метод whenPivotLoaded принимает имя сводной таблицы в качестве первого аргумента. Вторым аргументом должно быть замыкание, которое возвращает значение, которое будет возвращено, если в модели доступна сводная информация:
** * Transform the resource into an array. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'expires_at' => $this->whenPivotLoaded('role_user', function () { return $this->pivot->expires_at; }), ]; }
Вместо коллекции записей в ресурсный класс можно передать объект пагинатора:
public function index(){ return new ProductResourceCollection(Product::paginate(10)); }
Тогда JSON-объект сгенерированный ресурсным классом будет содержать следующие свойства:
Класс ресурсной коллекции содержит еще два метода:
public function index(){ return new ProductResourceCollection(Product::paginate(10))->withQuery(['search'=>'Laravel']); }
К результирующему JSON-объекту можно добавить дополнительные данные, привязав их к ответу, или к заголовку.
public function index(Product $product){ return new ProductResource($product)->additional(['powered'=>'Laravel']); }
public function index(Product $product){ return new ProductResource($product)->response()->header('X-Data-Kind', 'Product'); }
Sanctum Laravel - это авторизация для API. Используется в бэкенд-приложениях.
Sanctum решает две проблемы: формирование API токена и аутентификации в приложении.
Установка и настройка
Сперва необходимо установить плагин с помощью composer
composer require laravel/sanctum
Далее публикуем необходимые настройки:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Sanctum создаст нужную миграцию для хранения токенов. Миграцию необходимо выполнить:
php artisan migrate
Далее находим файл App\Http\Kernel.php
и заменяем значение api
'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ],
Model User
Далее открываем модель User, и вносим сюда следующие изменения:
use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; }
Middleware
Теперь для защиты маршрутов в API мы можем использовать middleware auth:sanctum
:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Данный middleware уже используется в файле routes/api.php.
. Впоследствии перепишем его, создав группу для middleware auth:sanctum
Request UserRequest
Валидировать данные, необходимые для регистрации пользователя будем с помощью класса UserRequest. Сперва создадим его:
php artisan make:request UserRequest
Внесем необходимые изменения в класс:
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class UserRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'name'=>'required|string', 'email'=>'required|string|unique:users|email', 'password'=>'required|confirmed' ]; } }
Таким образом, со стороны фронта нужно будет отправить следующие request-ы: name
, email
, password
, password_confirmation
Controller AuthController
Создадим контроллер AuthController
php artisan make:controller AuthController
Добавим метод регистрации register
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\User; use App\Http\Requests\UserRequest; use Illuminate\Support\Facades\Hash; class AuthController extends Controller { public function register(UserRequest $r){ $r['password'] = Hash::make($r->password); $user = User::create($r->all()); $token = $user->createToken('myapptoken')->plainTextToken; $response = [ 'user'=>$user, 'token' => $token ]; return response()->json($response); } }
Маршрутизация
Для настройки маршрутов сперва в файл routes/api.php
подключим namespace к папке с контроллерами.
use App\Http\Controllers;
Теперь можем прописать нужные маршруты, разбив наши маршуты пока на две группы - открытые (public) и защищенные (protected) маршруты:
//public routes Route::post('register', [Controllers\AuthController::class, 'register']); //protected routes Route::group(['middleware'=>['auth:sanctum']], function(){ });
Postmen
Для тестирования запросов на бэк воспользуемся postmen. В папке с приложением создадим папку с коллекцией Auth и добавим туда нужные запросы. Обратие внимание на Headers. В заголовках должен быть отправлен Accept со значением application/json. После выставления заголовков, можно переключиться на вкладку params и там заполнить нужные для авторизации поля. После нажатия на кнопку send, при успешной авторизации мы увидим в ответе объект пользователя и token:
После того, как мы получили token, мы можем использовать его для защищённых запросов. Скопируем значение токена, и добавим его в значение Bearer Token вкладки Auth
Login и logout
Добавим в контроллер AuthController экшн входа - login.
public function login(Request $request){ $user = User::where('email', $request->email)->first(); if(!$user || !Hash::check($request->password, $user->password)){ return response()->json([ 'message' => 'bad credits' ]); } $token = $user->createToken('myapptoken')->plainTextToken; $response = [ 'user' => $user, 'token' => $token ]; return response()->json($response); }
Экшн выхода logout:
public function logout(Request $request) { auth()->user()->tokens()->delete(); return response()->json(['message'=>'Logged out']); }