Laravel · Часть 1 — Зачем фреймворк, Composer и установка Laravel
1 / 1
lesson.laravel · часть 1

Часть 1 — Зачем фреймворк, Composer и установка Laravel

Разбираем, что фреймворк забирает на себя по сравнению с «чистым PHP», смотрим, как Composer ставит пакеты, на эксперименте из одной папки — и устанавливаем Laravel одной командой: composer create-project laravel/laravel project-laravel. К концу части на диске лежит каркас приложения со 110 пакетами, и мы знаем, зачем нужен каждый файл в его корне.

Что понадобится: Linux Mint 22 (база Ubuntu 24.04) со стеком из блока Linux — nginx + PHP-FPM от своего пользователя, MariaDB, dnsmasq с зоной .rs; PHP 8.3; Composer 2.7+; около 100 МБ свободного места.

Laravel 13 PHP 8.3.6 110 пакетов · 76 МБ

1листайте
Часть 1 · Зачем фреймворк

Курс закончился на «чистом PHP». Что болит

В HTTP-блоке вы собрали рабочую систему с нуля: брони авиабилетов, профили пассажиров, статистику по странам. Всё руками: единая точка подключения includes.php, свой класс layout с методом render(), свой класс пагинации со страницами через ceil(), профиль по параметру pid в URL. Это сила — вы понимаете, как всё устроено изнутри. Но посмотрите на цену:

  • Роутинг руками. Кто обрабатывает запрос, решает if/else по $_GET. Каждая новая страница — новая ветка; про «неправильный» параметр надо не забыть вернуть 404.
  • Вёрстка-каркас руками. Класс layout — уже шаг к шаблонам, но вы написали его сами под один проект. В следующем проекте будете писать заново.
  • Защита руками. В блоке «SQL-инъекции» один непроверенный параметр sort открывал базу через UNION. Белый список и prepared statements работают — но только там, где вы про них вспомнили.
  • Подключение к БД руками. DSN, логин, пароль — прямо в PHP-файле; в блоке «Базы данных» так и было.
  • Структура «как получится». Процедурный код растёт куда глаза глядят; блок ООП начинался ровно с этого — со спагетти-кода, который не масштабируется.
Дело не в том, что «чистый PHP плохой»

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

2листайте
Часть 1 · Зачем фреймворк

Фреймворк — это договорённость плюс готовый каркас

Фреймворк — готовый каркас приложения плюс договорённость, как добавлять в него свой код:

  • каркас написан и проверен за вас: приём запроса, разбор URL, экранирование вывода, запросы к БД, сессии;
  • договорённость описывает, где живёт ваш код: маршруты — тут, контроллеры — там, шаблоны — в такой-то папке.

Эту схему вы уже знаете дважды:

  • nginx (lesson.linux.06): вы не писали веб-сервер — вы писали конфиг по договорённости: root, index, fastcgi_pass. Всё остальное nginx делает сам.
  • Bootstrap (HTTP-блок): фронтенд-фреймворк — договорённость о классах (container, btn, table) и готовый каркас стилей на все случаи.

Библиотеку вызываете вы

php
// курс: вы решаете, когда и что вызвать$data = json_decode($json, true);$hash = password_hash($pass, PASSWORD_DEFAULT);

Фреймворк вызывает вас

php
// вы лишь объявляете, что делать// при таком URL, — запрос фреймворк// поймает и вызовет ваш код самRoute::get('/users', [UserController::class, 'index']);

Такую строку напишем в части 3.

3листайте
Часть 1 · Зачем фреймворк

MVC одной картинкой

Договорённость Laravel называется MVC — Model-View-Controller, «модель-вид-контроллер». Путь данных внутри приложения:

HTTP-запрос — браузер → nginx → PHP-FPMHTTP-запросбраузер → nginx →PHP-FPMМаршрут — routes/web.phpМаршрутroutes/web.phpКонтроллер — app/Http/… — принимает запрос, проверяет данныеКонтроллерapp/Http/… —принимает запрос,проверяет данныеМодель — app/Models/… — запросы к БДМодельapp/Models/… —запросы к БДШаблон — resources/views/ — превращает данные в HTMLШаблонresources/views/ —превращает данные вHTMLОтвет — HTML / JSON / редирект — обратно в браузерОтветHTML / JSON /редирект — обратно вбраузерURL → кодURL → кодHTTP-запрос — браузер → nginx → PHP-FPMHTTP-запросМаршрут — routes/web.phpМаршрутКонтроллер — app/Http/… — принимает запрос, проверяет данныеКонтроллерМодель — app/Models/… — запросы к БДМодельШаблон — resources/views/ — превращает данные в HTMLШаблонОтвет — HTML / JSON / редирект — обратно в браузерОтвет
Путь запроса внутри приложения

В lesson.http.i.01 вы прошли путь запроса снаружи: браузер → DNS → nginx → PHP-FPM → index.php → буфер вывода → ответ. MVC начинается там, где раньше начинался ваш единый большой index.php: внутри скрипта вместо одной простыни кота по очереди работают четыре роли.

Обещание части 5

К концу части 5 каждая стрелка этой схемы будет живым кодом, который вы написали сами: маршрут /users, контроллер UserController, модель User, шаблоны списка и форм.

4листайте
Часть 1 · Стенд

Окружение: какая машина и какой PHP

Laravel 13 не установится на PHP старее 8.3 — это требование записано прямо в composer.json каркаса (строка "php": "^8.3", увидим её ниже). На вашем Mint 22 PHP 8.3 стоит ещё с lesson.linux.04 — проверяем.

Все терминалы урока — настоящие захваты. Команды выполнены на стенде, повторяющем ваш: Ubuntu 24.04 — та же база, на которой стоит Linux Mint 22, пользователь student, проекты в ~/recoursia/. В выводе hostnamectl честно видно, что стенд — контейнер: его собрали специально для съемки этого урока.

student@mint: ~576 мс
student@mint:~$
Что за машина — Стенд захвата: Ubuntu 24.04 — та же база, на которой стоит Linux Mint 22 у слушателей.
Разбор команды

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

hostnamectl
ТокенЗачем он и что меняет
hostnamectlутилита systemd: печатает «паспорт» машины — имя, ОС, ядро, виртуализацию. Аргументов и sudo не требует.

Что произойдёт

Девять строк: hostname, chassis, Machine ID, Boot ID, виртуализация, ОС, ядро, архитектура.

На что смотреть

Строка 1 — hostname mint, строка 7 — Ubuntu 24.04.4 LTS: та же база, на которой стоит Linux Mint 22 у слушателей. Chassis: container и Virtualization: docker честно говорят, что стенд урока — контейнер, собранный для съёмки.

Частые ошибки

  • hostnamectl опрашивает systemd: там, где systemd не запущен как PID 1, команда вместо паспорта выдаст ошибку подключения к bus.

PHP — 8.3.6, выше минимальных 8.3:

student@mint: ~478 мс
student@mint:~$
Версия PHP — Laravel 13 требует PHP 8.3+.
Разбор команды

Laravel 13 не установится на PHP старее 8.3, поэтому версию проверяем до установки, а не по сообщению об ошибке.

php -v
ТокенЗачем он и что меняет
phpисполняемый файл интерпретатора CLI; в Ubuntu/Mint его ставит пакет php8.3-cli.
-vфлаг версии: напечатать версию PHP, дату сборки и версию Zend Engine, после чего сразу завершиться.

Что произойдёт

Четыре строки: PHP 8.3.6 (cli) (built: Sep 2 2026 12:56:02) (NTS), копирайт, Zend Engine v4.3.6 и строка про OPcache.

На что смотреть

8.3.6 выше минимальных 8.3, которые требует каркас ("php": "^8.3" в его composer.json). Пометка (cli) — версия показана для консольного PHP: у PHP-FPM свой бинарник и свой php.ini. 8.3 — значение стенда; подставьте свою версию (php -v).

Частые ошибки

  • php: command not found — пакет php8.3-cli не установлен или не в PATH; без CLI не заработает и Composer.
Версия PHP

8.3 и php8.3 в командах выше — версия стенда. Проверьте свою: php -v. Если она другая — замените 8.3 на свою во всех путях (/etc/php/…, /run/php/…), именах пакетов (php8.3-*) и сервисе (php8.3-fpm).

5листайте
Шаг 0 · Версия PHP

Сначала — ваша версия PHP; дальше в примерах 8.3

php -v на прошлом слайде напечатал 8.3.6 — это версия стенда. Та же версия попадается в командах урока в шести местах: имена пакетов, конфиг CLI, конфиг FPM, пул FPM, unix-сокет и имя сервиса. Поэтому шаг нулевой — узнать свою. php -v и php.ini вы уже трогали в lesson.linux.06, когда включали display_errors; теперь нужна короткая строка «мажор.минор»:

student@mint: ~/recoursia/project-laravel572 мс
student@mint:~/recoursia/project-laravel$
Короткий ответ: мажор.минор версии PHP — Эту строку подставляют в пути и имена пакетов/сервисов.
Разбор команды

Строку «мажор.минор» удобно получать программно: её подставляют в имена пакетов, пути конфигов, имя сокета и имя сервиса.

php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION, PHP_EOL;'
ТокенЗачем он и что меняет
phpинтерпретатор CLI.
-rвыполнить код, переданный аргументом, — без файла на диске.
'…'одинарные кавычки вокруг кода: shell отдаёт его PHP дословно и не трогает внутренние двойные кавычки.
echoпечатает аргументы подряд, без разделителя.
PHP_MAJOR_VERSIONконстанта PHP: мажорная версия (на стенде 8).
.оператор склейки строк в PHP.
"."строка-разделитель: двойные кавычки внутри одинарных с shell не конфликтуют.
PHP_MINOR_VERSIONконстанта PHP: минорная версия (на стенде 3).
,запятая в echo — второй аргумент, а не склейка: так перевод строки печатают отдельно.
PHP_EOLконстанта перевода строки.
;закрывает инструкцию PHP; точка с запятой внутри кода обязательна.

Что произойдёт

Печатает ровно одну строку — 8.3.

На что смотреть

Это та строка, из которой собираются php8.3-cli, /etc/php/8.3/…, /run/php/php8.3-fpm.sock и имя сервиса php8.3-fpm. 8.3 — стенд; подставьте свою версию (php -v).

Частые ошибки

  • Внешние кавычки должны быть одинарными: в двойных shell раскроет $ и подстановки, и до PHP доедет уже другой код.

Одна команда печатает все места, куда эта строка подставляется. V — ваша версия, дальше в таблице она же:

student@mint: ~/recoursia/project-laravel502 мс
student@mint:~/recoursia/project-laravel$
Где версия PHP встречается в путях и именах — Одна команда печатает все места, куда подставляется мажор.минор: пакеты, конфиги, сокет, сервис.
Разбор команды

Версия PHP зашита в имена пакетов, пути конфигов, имя сокета и имя сервиса. Одна команда печатает все эти места и тут же сверяет их с тем, что лежит на диске.

V=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
ТокенЗачем он и что меняет
V=присваивание переменной shell; пробела вокруг = быть не должно.
$(…)подстановка команды: то, что напечатает вложенный php, становится значением V.
php -r '…'код PHP в одинарных кавычках — склеивает мажор и минор версии в строку.

Что произойдёт

В переменную V попадает строка 8.3. Дальше её подставляют во все echo, то есть пути печатаются, а не выдумываются.

На что смотреть

Переменная живёт только в этой оболочке: в новом терминале её нет, команду повторяют целиком.

Частые ошибки

  • Пробел вокруг = (V = $(…)) shell читает как команду V с аргументами — будет command not found.
echo "версия: $V"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки: только в них shell раскрывает $V.
$Vзначение переменной — 8.3.

Что произойдёт

Первая строка вывода: версия: 8.3.

На что смотреть

С этой строки сверяют всё, что напечатано ниже.

Частые ошибки

  • В одинарных кавычках $V напечатался бы как текст, а не как версия.
echo "пакеты: php${V}-cli php${V}-fpm php${V}-mbstring php${V}-xml php${V}-curl php${V}-zip php${V}-mysql php${V}-sqlite3"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки, чтобы подстановка сработала.
${V}фигурные скобки явно показывают границу имени переменной перед дефисом.
php${V}-cliпакет интерпретатора CLI — тот, что запускает php и artisan.
php${V}-fpmпакет PHP-FPM — процесса, который отвечает за сайт в браузере.
php${V}-mbstring … php${V}-sqlite3пакеты расширений, нужных Laravel: mbstring, xml, curl, zip, mysql, sqlite3. Имя пакета = php + версия + суффикс.

Что произойдёт

Вторая строка вывода — восемь имён пакетов: php8.3-cli php8.3-fpm php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-mysql php8.3-sqlite3.

На что смотреть

Именно эти пакеты ставят через apt, если чего-то не хватает; все они уже стоят на стенде. 8.3 — стенд; подставьте свою версию (php -v).

Частые ошибки

  • Пакет без версии в имени (php-mbstring) — метапакет версии по умолчанию: он может потянуть не тот PHP.
echo "конфиги: /etc/php/$V/cli/php.ini и /etc/php/$V/fpm/php.ini, пул /etc/php/$V/fpm/pool.d/www.conf"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки для раскрытия $V.
/etc/php/$V/cli/php.iniконфиг PHP для консоли — его читает php и artisan.
/etc/php/$V/fpm/php.iniотдельный конфиг PHP-FPM — он влияет на сайт в браузере.
/etc/php/$V/fpm/pool.d/www.confфайл пула FPM: пользователь, группа, адрес сокета и права на него.

Что произойдёт

Третья строка вывода — три пути, в которые зашита версия.

На что смотреть

У CLI и FPM разные php.ini: правка одного не меняет другой. 8.3 — стенд; подставьте свою версию (php -v).

Частые ошибки

  • Поправили display_errors в cli/php.ini, а в браузере ничего не изменилось — FPM читает свой файл, и после правки сервис перезапускают.
echo "сокет: /run/php/php${V}-fpm.sock"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки для раскрытия ${V}.
/run/php/php${V}-fpm.sockunix-сокет, по которому nginx передаёт .php-запросы в PHP-FPM.

Что произойдёт

Четвёртая строка вывода — адрес сокета php8.3-fpm.sock.

На что смотреть

Этот путь пишут в fastcgi_pass конфига nginx (часть 2). Каталог /run живёт до перезагрузки, сокет создаёт сам сервис.

Частые ошибки

  • Чужая версия в пути (php8.4-fpm.sock) даёт в nginx connect() failed (2: No such file or directory) while connecting to upstream и 502 в браузере.
echo "сервис: php${V}-fpm"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки для раскрытия ${V}.
php${V}-fpmимя сервиса systemd, которым управляют через systemctl.

Что произойдёт

Пятая строка вывода — имя сервиса php8.3-fpm.

На что смотреть

Оно нужно для sudo systemctl restart php8.3-fpm после любой правки конфигов FPM. 8.3 — стенд; подставьте свою версию (php -v).

Частые ошибки

  • Unit php8.4-fpm.service could not be found — версия в имени не совпала с установленной.
ls /run/php/
ТокенЗачем он и что меняет
lsпоказать содержимое каталога.
/run/php/каталог рантайма PHP-FPM.

Что произойдёт

Три имени: php-fpm.sock, php8.3-fpm.pid, php8.3-fpm.sock.

На что смотреть

Проверка на месте, а не на глаз: файл php8.3-fpm.sock из $V совпал с тем, что реально лежит в каталоге.

Частые ошибки

  • No such file or directory — каталога нет, если FPM не установлен; пустой каталог — сервис не запущен.
Где версия встречается На стенде У вас
Пакеты apt php8.3-cli, php8.3-fpm, php8.3-mbstring, … (весь список напечатан выше) php$V-cli, php$V-fpm, php$V-mbstring, …
Конфиг CLI /etc/php/8.3/cli/php.ini /etc/php/$V/cli/php.ini
Конфиг FPM /etc/php/8.3/fpm/php.ini /etc/php/$V/fpm/php.ini
Пул FPM /etc/php/8.3/fpm/pool.d/www.conf /etc/php/$V/fpm/pool.d/www.conf
Unix-сокет /run/php/php8.3-fpm.sock /run/php/php$V-fpm.sock
Сервис systemd php8.3-fpm php$V-fpm
Своя строка версии php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' → 8.3 та же команда → ваша
Правило до конца урока

До конца урока в примерах стоит 8.3 — версия стенда. Если у вас другая версия, подставляйте свою в путях, пакетах, сервисе и сокете. Дальше эта сноска повторяется коротко под слайдами, где версия важна.

6листайте
Часть 1 · Стенд

Расширения PHP, которые нужны Laravel

Расширения не «доустанавливают по ходу» — их проверяют заранее, и не на глаз, а программно: функция extension_loaded() отвечает точно, собрано ли расширение в ваш PHP.

student@mint: ~575 мс
student@mint:~$
Расширения PHP, которые нужны Laravel — Проверяем не «на глаз», а программно: extension_loaded().
Разбор команды

Расширения PHP проверяют заранее и не на глаз: функция extension_loaded() отвечает точно, собрано ли расширение в этот интерпретатор.

php -r 'foreach (["ctype","curl","dom","fileinfo","filter","hash","mbstring","openssl","pcre","pdo","pdo_mysql","pdo_sqlite","session","simplexml","tokenizer","xml","zip","bcmath","intl","gd"] as $m) printf("%-12s %s\n", $m, extension_loaded($m) ? "есть" : "НЕТ");'
ТокенЗачем он и что меняет
phpинтерпретатор CLI.
-rвыполнить код из аргумента, без файла на диске.
'…'одинарные кавычки: shell не трогает код, поэтому $m и двойные кавычки внутри доезжают до PHP как есть.
foreachцикл по списку имён: одна итерация — одно расширение.
["ctype","curl",…"gd"]литерал массива из 20 имён расширений, которые нужны Laravel.
as $m$m — переменная цикла, текущее имя расширения.
printfформатированный вывод вместо echo — им делают ровные колонки.
"%-12s %s\n"формат: имя в поле шириной 12 с выравниванием влево, затем статус и перевод строки.
extension_loaded($m)функция PHP: загружено ли расширение в этот интерпретатор.
? "есть" : "НЕТ"тернарный оператор: печатаем спокойное «есть» или заметное «НЕТ».

Что произойдёт

20 строк вида ctype есть — по одной на расширение. На стенде все 20 со значением «есть».

На что смотреть

Ищите «НЕТ»: верхний регистр видно сразу. Критичны pdo_mysql и pdo_sqlite (база), mbstring (строки UTF-8), openssl (ключ APP_KEY), tokenizer и xml (шаблоны), curl, zip, fileinfo, gd, intl, bcmath.

Частые ошибки

  • «НЕТ» означает, что не стоит пакет php8.3-<имя>; после sudo apt install нужен sudo systemctl restart php8.3-fpm, и только потом — повторная проверка. Версия 8.3 — стенд, подставьте свою (php -v).

Все 20 на месте. Среди них — ваши старые знакомые по курсу:

  • pdo, pdo_mysql, pdo_sqlite — те самые расширения, через которые вы подключались к MariaDB в блоке «Базы данных»;
  • mbstring — функции mb_* из блока «Строки» (обычный strlen считает байты, а не символы UTF-8);
  • gd, intl, bcmath — картинки, форматирование чисел и дат по локали, точная арифметика без погрешностей float;
  • openssl, curl, zip — шифрование, HTTP-запросы, архивы.
Если чего-то не хватает

Ставится как любой пакет через apt (lesson.linux.04): sudo apt install php8.3-<имя>, затем перезапуск FPM: sudo systemctl restart php8.3-fpm.

Версия PHP

8.3 и php8.3 в командах выше — версия стенда. Проверьте свою: php -v. Если она другая — замените 8.3 на свою во всех путях (/etc/php/…, /run/php/…), именах пакетов (php8.3-*) и сервисе (php8.3-fpm).

7листайте
Часть 1 · Стенд

Что уже стоит — из курса Linux

Ни веб-сервер, ни базу сегодня ставить не придётся: nginx, PHP-FPM, MariaDB и dnsmasq установлены и настроены в lesson.linux.04–06. Проверяем, что все четыре сервиса живы:

student@mint: ~501 мс
student@mint:~$
Сервисы запущены — Все четыре понадобятся в уроке.
Разбор команды

Ни веб-сервер, ни базу сегодня ставить не придётся: проверяем одной командой, что все четыре сервиса из блока Linux запущены.

for s in nginx php8.3-fpm mariadb dnsmasq; do printf "%-14s %s\n" "$s" "$(systemctl is-active $s)"; done
ТокенЗачем он и что меняет
for s in nginx php8.3-fpm mariadb dnsmasqцикл shell по списку имён сервисов. Имя php8.3-fpm содержит версию: 8.3 — стенд, подставьте свою (php -v).
do … doneтело цикла: выполняется по разу на каждое имя из списка.
printfформатированный вывод — в отличие от echo умеет выравнивать колонки.
"%-14s %s\n"формат: имя сервиса в поле шириной 14 с выравниванием влево, затем статус и перевод строки.
"$s"текущее имя сервиса; двойные кавычки страхуют значение от разбиения по пробелам.
$(systemctl is-active $s)подстановка команды: статус сервиса становится вторым аргументом printf.
is-activeсубкоманда systemctl: печатает active, inactive, failed или activating и возвращает ненулевой код, если сервис не запущен.

Что произойдёт

Четыре ровные строки: nginx, php8.3-fpm, mariadb, dnsmasq — у всех статус active.

На что смотреть

Ищите любое значение кроме active: inactive — сервис остановлен, failed — упал при запуске, activating — ещё поднимается, сообщение про ненайденный юнит — такого сервиса в системе нет.

Частые ошибки

  • systemctl is-active не требует sudo и работает даже у не запущенного сервиса — он просто печатает inactive. Внутри цикла ненулевой код команды не прерывает вывод: строка напечатается со статусом, а не с ошибкой.

Две детали из блока Linux вернутся в уроке не раз.

nginx работает от вашего пользователя — в linux.05–06 вы заменили www-data в nginx.conf:

student@mint: ~435 мс
student@mint:~$
Пользователь nginx и его версия
Разбор команды

nginx в блоке Linux тоже перевели на вашего пользователя — проверяем директиву user и заодно версию сервера.

grep -n '^user' /etc/nginx/nginx.conf
ТокенЗачем он и что меняет
grepпоиск строк по шаблону.
-nномер строки в файле.
'^user'^ — начало строки: закомментированная #user www-data; в выдачу не попадает.
/etc/nginx/nginx.confглавный конфиг nginx; директива user живёт в нём, а не в конфигах сайтов.

Что произойдёт

Одна строка: 1:user student; — директива стоит в первой строке файла.

На что смотреть

Значение student совпадает с user/group пула FPM и с владельцем проекта: читать файлы проекта и писать в storage/ сможет именно он.

Частые ошибки

  • Пустой вывод — директива закомментирована, и nginx работает от пользователя по умолчанию (www-data): тогда права на проект и на сокет FPM придётся согласовывать заново. Правку применяют через sudo nginx -t и перезагрузку сервиса.
nginx -v
ТокенЗачем он и что меняет
nginxбинарник веб-сервера.
-vстрочная v: напечатать версию и выйти. Заглавная -V добавила бы флаги сборки и список модулей.

Что произойдёт

nginx version: nginx/1.24.0 (Ubuntu) — команда печатает это в stderr и завершается с кодом 0.

На что смотреть

Версия 1.24.0 — та же, что nginx подставляет в страницу ошибки (<center>nginx/1.24.0 (Ubuntu)</center>): по ней видно, что ответил nginx, а не PHP.

Частые ошибки

  • nginx -v ничего не проверяет: тест конфига делает nginx -t (в части 2 он даёт syntax is ok и test is successful), и запускать его нужно через sudo.

Пул PHP-FPM — тоже (настройки пула в www.conf из lesson.linux.06; на стенде пользователь student):

student@mint: ~482 мс
student@mint:~$
Пул PHP-FPM работает от своего пользователя — Так настроено в lesson.linux.06: user/group = student.
Разбор команды

Пул PHP-FPM работает от пользователя student — это настроили в lesson.linux.06. Именно поэтому Laravel сможет писать логи и кэш в свою папку storage/.

grep -nE '^(user|group|listen|listen.owner|listen.group|listen.mode) =' /etc/php/8.3/fpm/pool.d/www.conf
ТокенЗачем он и что меняет
grepпоиск строк по шаблону.
-nEдва флага слитно: -n печатает номера строк, -E включает расширенные регулярные выражения — (, ) и | пишутся без обратных слэшей.
'^(user|group|listen|…) ='шаблон: начало строки, затем одно из шести имён параметра, пробел и =. Якорь ^ отсекает комментарии, а пробел перед = — похожие параметры вроде listen.allowed_clients.
/etc/php/8.3/fpm/pool.d/www.confфайл пула www — пул по умолчанию в поставке Ubuntu.

Что произойдёт

Шесть строк: user = student (28), group = student (29), listen = /run/php/php8.3-fpm.sock (41), listen.owner = student (53), listen.group = student (54), listen.mode = 0660 (55).

На что смотреть

Строки 28–29: код сайта выполняется от того же пользователя, которому принадлежит проект, — права на запись в storage/ сходятся. Строка 41: адрес сокета совпадает с тем, что пишут в fastcgi_pass. Строки 53–55: владелец и права самого сокета — их задаёт FPM, а не nginx.

Частые ошибки

  • Если в пуле user = www-data, а проект принадлежит вам, PHP не сможет писать скомпилированные шаблоны и кэш: в логе появится local.ERROR: tempnam(): file created in the system's temporary directory, а браузер получит 500 (воспроизводим в части 6). Любая правка www.conf требует sudo systemctl restart php8.3-fpm. 8.3 в пути — стенд, подставьте свою версию (php -v).

Почему это важно именно для Laravel: приложение пишет на диск — логи, кэш, скомпилированные шаблоны — в папку storage/ внутри проекта. Пишет php-fpm. У нас он работает от того же пользователя, которому принадлежит проект, — права сойдутся. В части 6 мы специально сломаем права и посмотрим, как выглядят 500 из-за этого.

А этот unix-сокет — адрес, по которому nginx передаёт .php в FPM; в части 2 напишем его в fastcgi_pass:

student@mint: ~429 мс
student@mint:~$
Unix-сокет PHP-FPM — Этот адрес пишет nginx в fastcgi_pass.
Разбор команды

Смотрим сам unix-сокет: это адрес, по которому nginx передаёт .php-запросы в PHP-FPM, — в части 2 он попадёт в fastcgi_pass.

ls -l /run/php/
ТокенЗачем он и что меняет
lsсписок файлов каталога.
-lдлинный формат: тип файла, права, владелец, размер, дата.
/run/php/каталог рантайма PHP-FPM: здесь сервис создаёт сокет и pid-файл.

Что произойдёт

Три записи: симлинк php-fpm.sock → /etc/alternatives/php-fpm.sock, файл php8.3-fpm.pid (5 байт) и сокет php8.3-fpm.sock.

На что смотреть

Первый символ s в srw-rw---- — это сокет, а не обычный файл. Владелец student:student и права 0660 — результат listen.owner, listen.group и listen.mode из www.conf. Симлинк php-fpm.sock без версии ведёт в /etc/alternatives — так Ubuntu выбирает сокет версии по умолчанию.

Частые ошибки

  • Нет строки php8.3-fpm.sock — сервис не запущен, проверьте systemctl status php8.3-fpm. /run — tmpfs: после перезагрузки файлы создаёт сервис, а не вы вручную.
Версия PHP

8.3 и php8.3 в командах выше — версия стенда. Проверьте свою: php -v. Если она другая — замените 8.3 на свою во всех путях (/etc/php/…, /run/php/…), именах пакетов (php8.3-*) и сервисе (php8.3-fpm).

8листайте
Часть 1 · Стенд

Конфиг PHP: где лежит php.ini и display_errors

Чтобы поменять настройку PHP, надо знать, какой файл править. php --ini показывает, откуда CLI берёт конфигурацию:

student@mint: ~493 мс
student@mint:~$
/etc/php/8.3/cli/conf.d/20-calendar.ini,/etc/php/8.3/cli/conf.d/20-ctype.ini,/etc/php/8.3/cli/conf.d/20-curl.ini,/etc/php/8.3/cli/conf.d/20-dom.ini,/etc/php/8.3/cli/conf.d/20-exif.ini,/etc/php/8.3/cli/conf.d/20-ffi.ini,/etc/php/8.3/cli/conf.d/20-fileinfo.ini,/etc/php/8.3/cli/conf.d/20-ftp.ini,/etc/php/8.3/cli/conf.d/20-gd.ini,/etc/php/8.3/cli/conf.d/20-gettext.ini,/etc/php/8.3/cli/conf.d/20-iconv.ini,/etc/php/8.3/cli/conf.d/20-intl.ini,/etc/php/8.3/cli/conf.d/20-mbstring.ini,/etc/php/8.3/cli/conf.d/20-mysqli.ini,/etc/php/8.3/cli/conf.d/20-pdo_mysql.ini,/etc/php/8.3/cli/conf.d/20-pdo_sqlite.ini,/etc/php/8.3/cli/conf.d/20-phar.ini,/etc/php/8.3/cli/conf.d/20-posix.ini,/etc/php/8.3/cli/conf.d/20-readline.ini,/etc/php/8.3/cli/conf.d/20-shmop.ini,/etc/php/8.3/cli/conf.d/20-simplexml.ini,/etc/php/8.3/cli/conf.d/20-sockets.ini,/etc/php/8.3/cli/conf.d/20-sqlite3.ini,/etc/php/8.3/cli/conf.d/20-sysvmsg.ini,/etc/php/8.3/cli/conf.d/20-sysvsem.ini,/etc/php/8.3/cli/conf.d/20-sysvshm.ini,/etc/php/8.3/cli/conf.d/20-tokenizer.ini,/etc/php/8.3/cli/conf.d/20-xmlreader.ini,/etc/php/8.3/cli/conf.d/20-xmlwriter.ini,/etc/php/8.3/cli/conf.d/20-xsl.ini,/etc/php/8.3/cli/conf.d/20-zip.ini
Где лежит php.ini — Пригодится, когда правим display_errors или memory_limit.
Разбор команды

Чтобы поменять настройку PHP, надо знать, какой файл править: php --ini показывает, откуда CLI берёт конфигурацию.

php --ini
ТокенЗачем он и что меняет
phpинтерпретатор CLI.
--iniслужебный флаг: напечатать пути к php.ini и к дополнительным .ini и завершиться; код приложения не выполняется.

Что произойдёт

Три заголовка и список: каталог поиска php.ini, реально загруженный файл, каталог сканирования conf.d и 36 подключённых дополнительных .ini — от 10-mysqlnd.ini и 10-opcache.ini до 20-zip.ini.

На что смотреть

Строка 2 — Loaded Configuration File указывает на /etc/php/8.3/cli/php.ini: именно его правят для консоли и artisan. Строка 3 — каталог conf.d: расширения подключаются отдельными файлами, и в списке видно каждый из 36 (в том числе 20-pdo_mysql.ini, 20-mbstring.ini, 20-intl.ini). 8.3 — стенд; подставьте свою версию (php -v).

Частые ошибки

  • Loaded Configuration File: (none) — php.ini не найден, PHP работает на встроенных значениях, и правки настроек ни на что не влияют. И помните: это вывод CLI, у FPM свой файл /etc/php/8.3/fpm/php.ini.

Запомните путь /etc/php/8.3/cli/php.ini — и обратите внимание, что у CLI и у FPM свои php.ini: /etc/php/8.3/cli/ и /etc/php/8.3/fpm/. В lesson.linux.06 вы включили display_errors в обоих — поэтому ошибки видно и в браузере, и в консоли:

student@mint: ~419 мс
student@mint:~$
display_errors включён (lesson.linux.06) — Для разработки ошибки показываем; в проде — нет.
Разбор команды

Показ ошибок должен быть включён в обоих конфигах — и для консоли, и для FPM; иначе ошибка в браузере выглядит пустой страницей.

grep -n '^display_errors' /etc/php/8.3/cli/php.ini /etc/php/8.3/fpm/php.ini
ТокенЗачем он и что меняет
grepпоиск строк по шаблону.
-nпечатать номер строки — по нему удобно открывать файл в редакторе.
'^display_errors'шаблон в одинарных кавычках: ^ означает начало строки, поэтому закомментированные ;display_errors в выдачу не попадают.
/etc/php/8.3/cli/php.iniпервый файл — конфиг CLI.
/etc/php/8.3/fpm/php.iniвторой файл — конфиг FPM; он отвечает за ошибки в браузере.

Что произойдёт

Две строки: в обоих файлах на строке 518 стоит display_errors = On.

На что смотреть

Когда файлов несколько, grep сам подставляет в начало строки имя файла — по нему видно, что включено и там, и там. 8.3 в путях — стенд; подставьте свою версию (php -v).

Частые ошибки

  • Пустой вывод — настройка закомментирована или версия в пути чужая. После правки fpm/php.ini нужен sudo systemctl restart php8.3-fpm, иначе изменения не применятся. display_errors = On — режим разработки: на бою его выключают, а у Laravel показ ошибок регулируется ещё и APP_DEBUG в .env.
display_errors = On — режим разработки

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

Версия PHP

8.3 и php8.3 в командах выше — версия стенда. Проверьте свою: php -v. Если она другая — замените 8.3 на свою во всех путях (/etc/php/…, /run/php/…), именах пакетов (php8.3-*) и сервисе (php8.3-fpm).

9листайте
Часть 1 · Стенд

Локальная доменная зона .rs

В lesson.linux.05 вы настроили dnsmasq: любое имя зоны .rs резолвится в 127.0.0.1 — на ваш же компьютер. Весь настройка — файл из пяти строк:

student@mint: ~387 мс
student@mint:~$
Файл локальной зоны .rs — Как в lesson.linux.05.
Разбор команды

Локальную зону .rs настроили в lesson.linux.05: показываем файл, из которого dnsmasq узнаёт, что все её имена ведут на этот компьютер.

cat /etc/dnsmasq.d/rs.conf
ТокенЗачем он и что меняет
catнапечатать содержимое файла.
/etc/dnsmasq.d/rs.confфайл в каталоге drop-in: dnsmasq читает все *.conf из /etc/dnsmasq.d/, поэтому основной конфиг не правят.

Что произойдёт

Пять строк: комментарий, address=/rs/127.0.0.1, no-resolv и два server= — 1.1.1.1 и 8.8.8.8.

На что смотреть

address=/rs/127.0.0.1 — любое имя зоны .rs резолвится в loopback, то есть на этот же компьютер. no-resolv запрещает брать серверы из /etc/resolv.conf, а server= задаёт их явно: всё, что не .rs, уходит на 1.1.1.1 и 8.8.8.8.

Частые ошибки

  • Правка файла ничего не меняет, пока сервис не перезапущен: sudo systemctl restart dnsmasq. Ошибка в синтаксисе зоны (например, пропущенные слэши в address=/rs/…) молча оставляет имя без ответа.

Домен будущего приложения — project-laravel.rs: приложение про пользователей, зона локальная, домен бесплатно «выдаётся» dnsmasq. Сайта за ним пока нет, но зона уже отвечает:

student@mint: ~1440 мс
student@mint:~$
Зона .rs отвечает — Домен будущего приложения уже резолвится в 127.0.0.1.
Разбор команды

Домен будущего приложения уже резолвится — проверяем это двумя командами: через системный резолвер и через пинг.

getent hosts project-laravel.rs
ТокенЗачем он и что меняет
getentспрашивает системные базы данных через NSS — тем же путём, которым имя резолвит любое приложение.
hostsкакую базу спрашивать: соответствие «имя → адрес» (/etc/hosts и DNS).
project-laravel.rsимя будущего приложения в локальной зоне .rs.

Что произойдёт

Одна строка: 127.0.0.1 project-laravel.rs.

На что смотреть

getent показывает, каким адрес увидят программы на этой машине, а не только браузер: строка есть — значит nginx, curl и PHP разрешат имя так же.

Частые ошибки

  • Пустой вывод — dnsmasq не запущен или системный резолвер не указывает на 127.0.0.1; getent при ненайденном имени завершается с кодом 2.
ping -c 2 -W 2 project-laravel.rs
ТокенЗачем он и что меняет
pingпроверяет доступность адреса ICMP-запросами; заодно сам резолвит имя.
-c 2count: отправить два пакета и завершиться — без флага ping работает бесконечно.
-W 2таймаут ожидания ответа на пакет, в секундах.
project-laravel.rsто же имя, что и у getent.

Что произойдёт

Строка PING project-laravel.rs (127.0.0.1) 56(84) bytes of data., два ответа 64 bytes from localhost (127.0.0.1), статистика 2 packets transmitted, 2 received, 0% packet loss, time 1010ms и rtt min/avg/max/mdev = 0.038/0.046/0.054/0.008 ms.

На что смотреть

В скобках после имени — адрес, в который оно преобразовано: 127.0.0.1, то есть этот же компьютер. 0% packet loss — зона отвечает.

Частые ошибки

  • ping проверяет сеть, а не сайт: 0% packet loss при ненастроенном nginx — норма, веб-сервер настраиваем в части 2. Если ICMP запрещён, ответов не будет, но DNS может работать — тогда верьте строке getent.

project-laravel.rs → 127.0.0.1, пинг проходит. В части 2 настроим на этот домен nginx — и откроем сайт в браузере.

10листайте
Часть 1 · Стенд

MariaDB на месте

student@mint: ~456 мс
student@mint:~$
Клиент MariaDB — MariaDB стоит из lesson.linux.04.
Разбор команды

MariaDB стоит с lesson.linux.04 — весь блок «Базы данных» пройден на ней. Проверяем, что клиент на месте.

mysql --version
ТокенЗачем он и что меняет
mysqlклиент командной строки; в MariaDB он называется так же, как в MySQL.
--versionнапечатать версию и выйти: подключения к серверу не происходит.

Что произойдёт

mysql Ver 15.1 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper.

На что смотреть

Два числа: 15.1 — версия клиента MariaDB, 10.11.14-MariaDB — версия дистрибутива. Это версия клиента; версию сервера показывает только подключение к нему.

Частые ошибки

  • Команда работает и при остановленном сервере — «клиент есть» не значит «база запущена», это проверяет systemctl is-active mariadb. Вход от root по TCP на Ubuntu заканчивается ERROR 1698 (28000): Access denied for user 'root'@'localhost': у root аутентификация через unix-сокет, поэтому приложению в части 4 заводят своего пользователя со своей базой.

MariaDB 10.11 — установлена в lesson.linux.04, весь блок «Базы данных» вы проработали на ней. Сам Laravel по умолчанию обходится без неё: каркас заводится с файлом SQLite (увидите при установке и в .env). Но в части 4 мы переведём приложение на MariaDB — будет та же боевая схема, что в курсе.

Анонс части 4

На Ubuntu root у MariaDB входит через unix-сокет, а не по паролю через TCP — приложение так подключиться не сможет, да и не должно: у приложения будет свой пользователь с правами только на свою базу. Тот же принцип минимума прав, что и в блоке «SQL-инъекции», только на уровне всей БД.

11листайте
Часть 1 · Composer

Composer — пакетный менеджер PHP

Composer ставит в проект PHP-пакеты по имени, сам разруливает их зависимости, фиксирует версии и подключает автозагрузку классов. Ничего нового по сути: то же самое вы делаете apt для системных программ с lesson.linux.04. «Поставь nginx» — apt находит пакет в репозитории, скачивает вместе с зависимостями, устанавливает. «Поставь библиотеку для дат» — Composer делает ровно это, только для PHP-кода.

Он уже стоит на машине:

student@mint: ~448 мс
student@mint:~$
Composer — пакетный менеджер PHP
Разбор команды

Composer — пакетный менеджер PHP, и он уже стоит на машине: все установки в этой части сделаны именно им.

composer --version
ТокенЗачем он и что меняет
composerисполняемый файл пакетного менеджера; ставится из apt и работает без sudo, потому что пишет внутрь проекта.
--versionнапечатать версию и дату её сборки, затем выйти.

Что произойдёт

Composer version 2.7.1 2024-02-09 15:26:28.

На что смотреть

2.7.1 — версия стенда, дата справа — дата релиза. Ту же версию позже покажет php artisan about в блоке Environment, так что её можно сверить.

Частые ошибки

  • composer: command not found — пакет не установлен или не в PATH. sudo composer … внутри проекта оставит в vendor/ файлы, принадлежащие root, и следующие команды от вашего пользователя упрутся в права.

Версия 2.7.1 — в выводе даже дата её сборки. Всё, что дальше происходит в терминалах урока, сделано именно ею.

12листайте
Часть 1 · Composer

Composer vs apt: идея та же, масштаб другой

Вопрос apt (lesson.linux.04) Composer
Что ставит системные программы и библиотеки PHP-пакеты: библиотеки и каркасы проектов
Откуда репозитории Ubuntu Packagist — репозиторий PHP-пакетов
Куда ставит системные каталоги (/usr, /etc) папку vendor/ внутри проекта
Что запоминает базу пакетов системы (dpkg) composer.json + composer.lock проекта
Кто имеет право root (sudo) любой пользователь, sudo не нужен
Один на всех? да, пакет один на систему нет: у каждого проекта свои vendor/ и версии

Последняя строка — главное отличие. nginx на компьютере один. А библиотека Carbon может быть версии 3 в одном проекте и версии 2 в соседнем — и проекты не мешают друг другу: у каждого своя папка vendor/. Поэтому Composer и не ставит «в систему» — он ставит «в эту папку».

И заметьте: в захватах выше ни одна команда composer не потребовала sudo — файлы в проекте принадлежат тому же student, от которого он запущен.

13листайте
Часть 1 · Composer на практике

Эксперимент: папка и один скрипт

Не будем верить документации на слово — посмотрим на Composer вживую на эксперименте из пяти шагов. Создаём папку рядом с будущими проектами:

student@mint: ~456 мс
student@mint:~$
Пустая папка для эксперимента с Composer
Разбор команды

Эксперимент с Composer начинаем в отдельной папке рядом с проектами курса — чтобы не смешивать его с установкой Laravel.

mkdir -p ~/recoursia/composer-demo
ТокенЗачем он и что меняет
mkdirсоздать каталог.
-pсоздавать недостающие родительские каталоги и не считать ошибкой уже существующий — команду можно запускать повторно.
~/recoursia/composer-demo~ shell раскрывает в домашний каталог /home/student; папка создаётся рядом с будущими проектами.
&&следующая команда выполнится, только если эта завершилась с кодом 0.

Что произойдёт

Каталог создан (или уже существовал). Цепочка && гарантирует, что в него зайдут только при успехе.

На что смотреть

Никакого sudo: папка внутри /home/student, владельцем остаётся student — как и у всех файлов Composer дальше.

Частые ошибки

  • Без -p повторный запуск даст mkdir: cannot create directory …: File exists; если заменить && на ;, shell продолжит работу даже после ошибки.
cd ~/recoursia/composer-demo
ТокенЗачем он и что меняет
cdсменить текущий каталог.
~/recoursia/composer-demoтот же путь, что и у mkdir, — папка эксперимента.

Что произойдёт

Все следующие команды выполняются внутри composer-demo.

На что смотреть

Composer всегда работает с текущим каталогом: где вы стоите, там и появятся composer.json, composer.lock и vendor/.

Частые ошибки

  • cd действует только в этом окне терминала: в новой вкладке вы снова в домашнем каталоге.
pwd
ТокенЗачем он и что меняет
pwdнапечатать текущий каталог — проверка, что cd сработал.

Что произойдёт

Строка /home/student/recoursia/composer-demo.

На что смотреть

Привычка из блока Linux: перед командой, которая что-то создаёт, убедиться, что вы стоите там, где думаете.

Частые ошибки

  • Без pwd легко создать vendor/ в соседнем проекте — Composer подтверждений не спрашивает.
ls -la
ТокенЗачем он и что меняет
lsсписок файлов: проверяем, что в новой папке.
-laдва флага слитно: -l — длинный формат (права, владелец, размер, дата), -a — показывать скрытые записи, начинающиеся с точки.

Что произойдёт

total 12, записи . и .. и один файл — now.php, 220 байт, владелец student.

На что смотреть

Папка почти пустая: в ней только заранее написанный now.php, а composer.json ещё не создан — его Composer заведёт сам на следующем шаге. Никакого sudo: владелец всех записей — student.

Частые ошибки

  • Если папка не пустая и composer.json в ней уже есть, следующий composer require обновит его, а не создаст. ls без -a скрывает dot-файлы: в корне Laravel-проекта так не увидеть ни .env, ни .gitignore.

Папка почти пустая: только скрипт now.php (220 байт), который мы написали в редакторе заранее — доберёмся до него после установки пакета. Обратите внимание: composer.json здесь ещё нет.

14листайте
Часть 1 · Composer на практике

composer require: один пакет превращается в девять

Carbon — библиотека для работы с датами: «через неделю», «3 дня назад», локализация. В самом уроке она нам не понадобится — важна механика. Из папки проекта, без sudo, одна команда:

student@mint: ~/recoursia/composer-demo5178 мс
student@mint:~/recoursia/composer-demo$
Writing lock fileInstalling dependencies from lock file (including require-dev)Package operations: 9 installs, 0 updates, 0 removals  - Downloading symfony/translation-contracts (v3.7.1)  - Downloading symfony/polyfill-mbstring (v1.43.0)  - Downloading symfony/deprecation-contracts (v3.7.1)  - Downloading symfony/translation (v7.4.17)  - Downloading symfony/polyfill-php83 (v1.43.0)  - Downloading psr/clock (1.0.0)  - Downloading symfony/clock (v7.4.21)  - Downloading carbonphp/carbon-doctrine-types (3.2.1)  - Downloading nesbot/carbon (3.14.2) 0/9 [>---------------------------]   0% 1/9 [===>------------------------]  11% 5/9 [===============>------------]  55% 7/9 [=====================>------]  77% 8/9 [========================>---]  88% 9/9 [============================] 100%  - Installing symfony/translation-contracts (v3.7.1): Extracting archive  - Installing symfony/polyfill-mbstring (v1.43.0): Extracting archive  - Installing symfony/deprecation-contracts (v3.7.1): Extracting archive  - Installing symfony/translation (v7.4.17): Extracting archive  - Installing symfony/polyfill-php83 (v1.43.0): Extracting archive  - Installing psr/clock (1.0.0): Extracting archive  - Installing symfony/clock (v7.4.21): Extracting archive  - Installing carbonphp/carbon-doctrine-types (3.2.1): Extracting archive  - Installing nesbot/carbon (3.14.2): Extracting archive 0/9 [>---------------------------]   0% 8/9 [========================>---]  88% 9/9 [============================] 100%Generating autoload files8 packages you are using are looking for funding.Use the `composer fund` command to find out more!No security vulnerability advisories found.Using version ^3.14 for nesbot/carbon
Первая установка пакета — Carbon — библиотека для работы с датами; нужна только чтобы увидеть, что делает Composer.
Разбор команды

Carbon — библиотека для работы с датами. В самом уроке она не понадобится: важна механика — что Composer делает сам, когда у него просят один пакет.

composer require nesbot/carbon
ТокенЗачем он и что меняет
composerпакетный менеджер PHP; запущен от student и без sudo — всё пишется в текущую папку.
requireсубкоманда «добавить зависимость и установить её»; если composer.json ещё нет, Composer создаёт его сам.
nesbot/carbonимя пакета в формате издатель/имя — по нему пакеты ищутся в Packagist.

Что произойдёт

За 5,2 с Composer создал composer.json, построил дерево зависимостей (на один запрошенный пакет — девять), зафиксировал их в composer.lock, скачал и распаковал в vendor/ и сгенерировал автозагрузчик. Вывод — 49 строк.

На что смотреть

Строки-вехи по порядку: ./composer.json has been created (1) → Lock file operations: 9 installs, 0 updates, 0 removals (5) → девять строк - Locking … с точными версиями, среди них - Locking nesbot/carbon (3.14.2) (7) → Writing lock file (15) → Package operations: 9 installs, 0 updates, 0 removals (17) → девять строк - Downloading … и прогресс 0/9 … 9/9 [============================] 100% → девять строк - Installing …: Extracting archive → Generating autoload files (45) → No security vulnerability advisories found. (48) → Using version ^3.14 for nesbot/carbon (49): версию мы не указывали, Composer выбрал 3.14.2 и записал в json диапазон ^3.14.

Частые ошибки

  • Повторный запуск той же команды ничего не поставит — Composer ответит, что в lock-файле и в vendor/ менять нечего; это не ошибка. Опечатка в имени пакета — Composer не найдёт его в Packagist. Запуск не из папки проекта создаст composer.json там, где вы стоите. sudo composer require оставит у файлов в vendor/ владельца root.

Разбираем вывод построчно:

  • ./composer.json has been created — composer.json не существовало, и Composer создал его сам: файл проекта «появился» из пустой папки.
  • Lock file operations: 9 installs — просили один пакет, а установится девять: сам Carbon 3.14.2 и его зависимости — symfony/translation, polyfill-mbstring, psr/clock и другие. Дерево зависимостей построил Composer — наша задача была назвать один пакет.
  • Generating autoload files — Composer генерирует автозагрузчик классов; через два слайда увидим его в деле.
  • No security vulnerability advisories found. — установленные версии сверены с базой известных уязвимостей.
  • Последняя строка — Using version ^3.14 for nesbot/carbon: версию мы не указывали, Composer выбрал актуальную и записал в composer.json как ^3.14 — «третья мажорная, не ниже 14, но не четвёртая».
Locking — все девять — в composer.lock, с точными версиямиLockingвсе девять — вcomposer.lock, сточными версиямиDownloading — архивы пакетовDownloadingархивы пакетовInstalling — Extracting archive → vendor/InstallingExtracting archive →vendor/Writing lock fileWriting lock fileLocking — все девять — в composer.lock, с точными версиямиLockingDownloading — архивы пакетовDownloadingInstalling — Extracting archive → vendor/Installing
Путь каждого пакета — Locking → Downloading → Installing

Вся установка заняла около пяти секунд — счётчик миллисекунд виден в шапке окна терминала.

15листайте
Часть 1 · Composer на практике

Что появилось на диске: json, lock, vendor

student@mint: ~/recoursia/composer-demo442 мс
student@mint:~/recoursia/composer-demo$
Что Composer создал на диске
Разбор команды

Смотрим, что осталось на диске после установки. Три вещи — json, lock и vendor/ — пойдут с нами дальше, в Laravel-проект.

ls -la
ТокенЗачем он и что меняет
lsсписок файлов.
-laдва флага слитно: -l — длинный формат, по нему видны размеры созданных файлов, -a — показывать скрытые.

Что произойдёт

total 48: composer.json (60 байт), composer.lock (26 314 байт), прежний now.php (220 байт) и каталог vendor.

На что смотреть

Сопоставьте размеры: json — «что мы хотим» — 60 байт, lock — «что реально стоит» — 26 314 байт точных версий. Все записи принадлежат student: Composer работал без sudo. Между командами в строке стоит пустой echo — он просто печатает пустую строку, чтобы блоки вывода не слипались.

Частые ошибки

  • Отсутствие composer.lock означает, что установка не закончилась: по одному composer.json точные версии пакетов не восстановить.
cat composer.json
ТокенЗачем он и что меняет
catнапечатать файл.
composer.jsonманифест проекта; зависимость в него дописал сам Composer.

Что произойдёт

JSON из одной секции require с единственным пакетом: "nesbot/carbon": "^3.14".

На что смотреть

^3.14 — диапазон «любая 3.x не ниже 3.14»: Composer выбрал версию сам. Точная версия каждого из пакетов зафиксирована только в composer.lock (26 314 байт).

Частые ошибки

  • Ручная правка composer.json пакет не ставит: после неё нужен composer update или composer install, иначе json и содержимое vendor/ разъедутся.
ls vendor
ТокенЗачем он и что меняет
lsсписок файлов.
vendorкаталог с кодом установленных пакетов.

Что произойдёт

Семь имён: autoload.php, bin, carbonphp, composer, nesbot, psr, symfony.

На что смотреть

Каталоги названы по издателям из имён пакетов: nesbot/carbon лежит в vendor/nesbot/. autoload.php — файл, который подключает скрипт; composer/ — служебные карты автозагрузки; bin/ — исполняемые файлы пакетов.

Частые ошибки

  • В vendor/ не правят код: изменения затрёт следующая установка. В git каталог не коммитят — он восстанавливается из composer.lock командой composer install.
du -sh vendor
ТокенЗачем он и что меняет
duсчитать место, занятое на диске.
-shдва флага слитно: -s — только итог по аргументу, без перечисления подкаталогов, -h — человекочитаемые единицы (K, M, G).
vendorкаталог с кодом установленных пакетов.

Что произойдёт

7.2M — столько весит один пакет вместе с восемью зависимостями.

На что смотреть

Сравните с 60 байтами composer.json: чужой код тяжёлый, свой — нет. В проекте Laravel весь проект займёт 76 МБ, и почти всё это будет vendor/.

Частые ошибки

  • du меряет занятые блоки, а не байты, поэтому его число чуть больше суммы размеров из ls -l.

Три новых вещи — и все три пойдут с нами дальше по уроку:

  • composer.json (60 байт) — что мы хотим. Пока одна зависимость: "nesbot/carbon": "^3.14" — та самая строка, которую Composer дописал сам.
  • composer.lock (26 314 байт) — что реально стоит: точные версии всех девяти пакетов.
  • vendor/ (7.2 МБ) — код пакетов и autoload.php. Внутри по именам издателей: nesbot/, symfony/, psr/, carbonphp/, и служебная папка composer/ с самим автозагрузчиком.

Зачем lock отдельным файлом, если есть json? ^3.14 — это диапазон: через месяц выйдет Carbon 3.15, и свежая установка возьмёт его, хотя вы тестировали 3.14.2. Lock-файл гарантирует: коллега, тестовый сервер и боевой хостинг получат ровно те же версии, что и вы. Отсюда правило, которое действует и в Laravel-проекте: composer.json и composer.lock коммитят в git вместе, а vendor/ — нет: он однозначно восстанавливается из lock одной командой composer install.

16листайте
Часть 1 · Composer на практике

Автозагрузка: ни одного require для классов

Тот самый now.php, который мы положили в папку заранее:

student@mint: ~/recoursia/composer-demo409 мс
student@mint:~/recoursia/composer-demo$
Скрипт, который использует Carbon без require — Файл создан в редакторе до этого шага.
Разбор команды

Показываем скрипт, ради которого ставили пакет: он работает с классом Carbon и не подключает ни одного файла с этим классом.

cat now.php
ТокенЗачем он и что меняет
catнапечатать содержимое файла.
now.phpскрипт, написанный в редакторе до установки пакета; 220 байт из вывода ls -la.

Что произойдёт

Семь строк PHP: подключение автозагрузчика, use Carbon\Carbon; и два echo — с Carbon::now() и с Carbon::now()->addWeek()->locale('ru')->diffForHumans().

На что смотреть

require ровно один — __DIR__ . '/vendor/autoload.php': __DIR__ даёт каталог самого скрипта, поэтому путь не зависит от того, откуда его запустили. Строка use Carbon\Carbon; — короткое имя пространства имён, а не подключение файла.

Частые ошибки

  • Без строки с autoload.php PHP ответит фатальной ошибкой о ненайденном классе Carbon\Carbon: автозагрузчик не зарегистрирован, и имя класса некому превратить в путь к файлу.

Запускаем:

student@mint: ~/recoursia/composer-demo509 мс
student@mint:~/recoursia/composer-demo$
Автозагрузка классов работает
Разбор команды

Запускаем скрипт — проверяем, что автозагрузка классов действительно работает, а не просто лежит файлом в vendor/.

php now.php
ТокенЗачем он и что меняет
phpинтерпретатор CLI: выполняет файл, путь к которому передан аргументом.
now.phpскрипт в текущей папке; относительный путь работает, потому что мы стоим в composer-demo.

Что произойдёт

Две строки: Сейчас: 2026-10-17 10:19:40 и Через неделю: через 6 дней.

На что смотреть

Класс Carbon доступен без единого require файла класса: автозагрузчик подключил его по правилу PSR-4 в момент обращения. «через 6 дней» вместо «через неделю» — признак живого прогона: diffForHumans() считает разницу в момент вызова.

Частые ошибки

  • Запуск вне папки (php composer-demo/now.php) тоже сработает — благодаря __DIR__. А вот запуск с опечаткой в имени даст Could not open input file: now.php, а удалённый vendor/ — ошибку про ненайденный autoload.php.

Смотрите внимательно: скрипт работает с классом Carbon, но не подключает ни одного файла с ним. require ровно один — vendor/autoload.php. Строка use Carbon\Carbon; — тоже не подключение файла: это короткое имя, чтобы не писать полное пространство имён каждый раз.

Как это работает: Composer знает из своих файлов, какой класс в каком пакете лежит, и autoload.php действует по правилу PSR-4: пространство имён отображается в путь на диске. Код обращается к Carbon\Carbon — автозагрузчик переводит имя в путь, находит файл класса внутри vendor/ и подключает его в момент обращения, а не заранее.

Было в курсе

php
// подключали файлы руками// и следили за порядком и путямиrequire_once 'db.php';require_once 'layout.php';require_once 'pagination.php';

Стало

php
// один require — и все классы// проекта и пакетов доступныrequire __DIR__ . '/vendor/autoload.php';

В проекте Laravel та же PSR-4 привяжет App\ к папке app/ — увидим это в composer.json через несколько слайдов.

Мелочь, выдающая живой прогон: последняя строка напечатала «через 6 дней», а не «через неделю» — diffForHumans() считает разницу в момент вызова, а к этому моменту от «недели» уже отвалились миллисекунды.

17листайте
Часть 1 · Установка Laravel

Установка Laravel: одна команда

Эксперимент закончен, возвращаемся в ~/recoursia. Вся установка приложения — одна команда: «возьми каркас laravel/laravel и создай из него проект в папке project-laravel»:

student@mint: ~/recoursia19430 мс
student@mint:~/recoursia$
  - Locking doctrine/inflector (2.1.0)  - Locking doctrine/lexer (3.0.3)  - Locking dragonmantank/cron-expression (v3.6.0)  - Locking egulias/email-validator (4.0.4)  - Locking fakerphp/faker (v1.24.1)  - Locking filp/whoops (2.18.6)  - Locking fruitcake/php-cors (v1.4.0)  - Locking graham-campbell/result-type (v1.2.0)  - Locking guzzlehttp/guzzle (8.2.0)  - Locking guzzlehttp/promises (3.0.2)  - Locking guzzlehttp/psr7 (3.1.0)  - Locking guzzlehttp/uri-template (v2.0.1)  - Locking hamcrest/hamcrest-php (v3.0.0)  - Locking laravel/agent-detector (v2.0.4)  - Locking laravel/framework (v13.37.0)  - Locking laravel/pail (v1.2.7)  - Locking laravel/pao (v1.1.5)  - Locking laravel/pint (v1.32.1)  - Locking laravel/prompts (v0.3.26)  - Locking laravel/serializable-closure (v2.1.0)  - Locking laravel/tinker (v3.0.2)  - Locking league/commonmark (2.10.3)  - Locking league/config (v1.2.0)  - Locking league/flysystem (3.36.0)  - Locking league/flysystem-local (3.35.3)  - Locking league/mime-type-detection (1.17.0)  - Locking league/uri (7.9.0)  - Locking league/uri-interfaces (7.9.0)  - Locking mockery/mockery (1.6.15)  - Locking monolog/monolog (3.12.1)  - Locking myclabs/deep-copy (1.14.0)  - Locking nesbot/carbon (3.14.2)  - Locking nette/schema (v1.3.6)  - Locking nette/utils (v4.1.5)  - Locking nikic/php-parser (v5.9.0)  - Locking nunomaduro/collision (v8.9.5)  - Locking nunomaduro/termwind (v2.4.0)  - Locking phar-io/manifest (2.0.4)  - Locking phar-io/version (3.2.1)  - Locking phpoption/phpoption (1.10.0)  - Locking phpunit/php-code-coverage (12.5.8)  - Locking phpunit/php-file-iterator (6.0.2)  - Locking phpunit/php-invoker (6.0.0)  - Locking phpunit/php-text-template (5.0.1)  - Locking phpunit/php-timer (8.0.0)  - Locking phpunit/phpunit (12.5.40)  - Locking psr/clock (1.0.0)  - Locking psr/container (2.0.2)  - Locking psr/event-dispatcher (1.0.0)  - Locking psr/http-client (1.0.3)  - Locking psr/http-factory (1.1.0)  - Locking psr/http-message (2.0)  - Locking psr/log (3.0.2)  - Locking psr/simple-cache (3.0.0)  - Locking psy/psysh (v0.12.24)  - Locking ramsey/collection (2.1.1)  - Locking ramsey/uuid (4.9.4)  - Locking sebastian/cli-parser (4.2.1)  - Locking sebastian/comparator (7.1.8)  - Locking sebastian/complexity (5.0.0)  - Locking sebastian/diff (7.0.1)  - Locking sebastian/environment (8.1.2)  - Locking sebastian/exporter (7.0.3)  - Locking sebastian/global-state (8.0.3)  - Locking sebastian/lines-of-code (4.0.1)  - Locking sebastian/object-enumerator (7.0.0)  - Locking sebastian/object-reflector (5.0.0)  - Locking sebastian/recursion-context (7.0.1)  - Locking sebastian/type (6.0.4)  - Locking sebastian/version (6.0.1)  - Locking staabm/side-effects-detector (1.0.5)  - Locking symfony/clock (v7.4.21)  - Locking symfony/console (v7.4.21)  - Locking symfony/css-selector (v7.4.18)  - Locking symfony/deprecation-contracts (v3.7.1)  - Locking symfony/error-handler (v7.4.20)  - Locking symfony/event-dispatcher (v7.4.17)  - Locking symfony/event-dispatcher-contracts (v3.7.1)  - Locking symfony/finder (v7.4.20)  - Locking symfony/http-foundation (v7.4.21)  - Locking symfony/http-kernel (v7.4.21)  - Locking symfony/mailer (v7.4.19)  - Locking symfony/mime (v7.4.19)  - Locking symfony/polyfill-ctype (v1.37.0)  - Locking symfony/polyfill-intl-grapheme (v1.43.0)  - Locking symfony/polyfill-intl-idn (v1.43.0)  - Locking symfony/polyfill-intl-normalizer (v1.43.0)  - Locking symfony/polyfill-mbstring (v1.43.0)  - Locking symfony/polyfill-php80 (v1.43.0)  - Locking symfony/polyfill-php82 (v1.43.0)  - Locking symfony/polyfill-php83 (v1.43.0)  - Locking symfony/polyfill-php84 (v1.43.0)  - Locking symfony/polyfill-php85 (v1.43.0)  - Locking symfony/polyfill-php86 (v1.43.0)  - Locking symfony/polyfill-uuid (v1.43.0)  - Locking symfony/process (v7.4.21)  - Locking symfony/routing (v7.4.21)  - Locking symfony/service-contracts (v3.7.3)  - Locking symfony/string (v7.4.21)  - Locking symfony/translation (v7.4.17)  - Locking symfony/translation-contracts (v3.7.1)  - Locking symfony/uid (v7.4.21)  - Locking symfony/var-dumper (v7.4.21)  - Locking theseer/tokenizer (2.0.1)  - Locking tijsverkoyen/css-to-inline-styles (v2.4.0)  - Locking vlucas/phpdotenv (v5.7.0)  - Locking voku/portable-ascii (2.1.1)Writing lock fileInstalling dependencies from lock file (including require-dev)Package operations: 110 installs, 0 updates, 0 removals  - Downloading doctrine/inflector (2.1.0)  - Downloading doctrine/lexer (3.0.3)  - Downloading dragonmantank/cron-expression (v3.6.0)  - Downloading psr/container (2.0.2)  - Downloading fakerphp/faker (v1.24.1)  - Downloading symfony/http-foundation (v7.4.21)  - Downloading fruitcake/php-cors (v1.4.0)  - Downloading symfony/polyfill-php82 (v1.43.0)  - Downloading symfony/polyfill-php80 (v1.43.0)  - Downloading psr/http-message (2.0)  - Downloading psr/http-factory (1.1.0)  - Downloading psr/http-client (1.0.3)  - Downloading guzzlehttp/psr7 (3.1.0)  - Downloading guzzlehttp/promises (3.0.2)  - Downloading guzzlehttp/guzzle (8.2.0)  - Downloading guzzlehttp/uri-template (v2.0.1)  - Downloading symfony/polyfill-intl-normalizer (v1.43.0)  - Downloading symfony/polyfill-intl-grapheme (v1.43.0)  - Downloading symfony/polyfill-ctype (v1.37.0)  - Downloading symfony/string (v7.4.21)  - Downloading symfony/service-contracts (v3.7.3)  - Downloading symfony/console (v7.4.21)  - Downloading nunomaduro/termwind (v2.4.0)  - Downloading voku/portable-ascii (2.1.1)  - Downloading phpoption/phpoption (1.10.0)  - Downloading graham-campbell/result-type (v1.2.0)  - Downloading vlucas/phpdotenv (v5.7.0)  - Downloading symfony/css-selector (v7.4.18)  - Downloading tijsverkoyen/css-to-inline-styles (v2.4.0)  - Downloading symfony/var-dumper (v7.4.21)  - Downloading symfony/polyfill-uuid (v1.43.0)  - Downloading symfony/uid (v7.4.21)  - Downloading symfony/routing (v7.4.21)  - Downloading symfony/process (v7.4.21)  - Downloading symfony/polyfill-php86 (v1.43.0)  - Downloading symfony/polyfill-php85 (v1.43.0)  - Downloading symfony/polyfill-php84 (v1.43.0)  - Downloading symfony/polyfill-intl-idn (v1.43.0)  - Downloading symfony/mime (v7.4.19)  - Downloading psr/event-dispatcher (1.0.0)  - Downloading symfony/event-dispatcher-contracts (v3.7.1)  - Downloading symfony/event-dispatcher (v7.4.17)  - Downloading psr/log (3.0.2)  - Downloading egulias/email-validator (4.0.4)  - Downloading symfony/mailer (v7.4.19)  - Downloading symfony/error-handler (v7.4.20)  - Downloading symfony/http-kernel (v7.4.21)  - Downloading symfony/finder (v7.4.20)  - Downloading ramsey/collection (2.1.1)  - Downloading brick/math (1.0.0)  - Downloading ramsey/uuid (4.9.4)  - Downloading psr/simple-cache (3.0.0)  - Downloading monolog/monolog (3.12.1)  - Downloading league/uri-interfaces (7.9.0)  - Downloading league/uri (7.9.0)  - Downloading league/mime-type-detection (1.17.0)  - Downloading league/flysystem-local (3.35.3)  - Downloading league/flysystem (3.36.0)  - Downloading nette/utils (v4.1.5)  - Downloading nette/schema (v1.3.6)  - Downloading dflydev/dot-access-data (v3.0.3)  - Downloading league/config (v1.2.0)  - Downloading league/commonmark (2.10.3)  - Downloading laravel/serializable-closure (v2.1.0)  - Downloading laravel/prompts (v0.3.26)  - Downloading laravel/framework (v13.37.0)  - Downloading laravel/pail (v1.2.7)  - Downloading laravel/agent-detector (v2.0.4)  - Downloading laravel/pao (v1.1.5)  - Downloading laravel/pint (v1.32.1)  - Downloading nikic/php-parser (v5.9.0)  - Downloading psy/psysh (v0.12.24)  - Downloading laravel/tinker (v3.0.2)  - Downloading hamcrest/hamcrest-php (v3.0.0)  - Downloading mockery/mockery (1.6.15)  - Downloading filp/whoops (2.18.6)  - Downloading nunomaduro/collision (v8.9.5)  - Downloading staabm/side-effects-detector (1.0.5)  - Downloading sebastian/version (6.0.1)  - Downloading sebastian/type (6.0.4)  - Downloading sebastian/recursion-context (7.0.1)  - Downloading sebastian/object-reflector (5.0.0)  - Downloading sebastian/object-enumerator (7.0.0)  - Downloading sebastian/global-state (8.0.3)  - Downloading sebastian/exporter (7.0.3)  - Downloading sebastian/environment (8.1.2)  - Downloading sebastian/diff (7.0.1)  - Downloading sebastian/comparator (7.1.8)  - Downloading sebastian/cli-parser (4.2.1)  - Downloading phpunit/php-timer (8.0.0)  - Downloading phpunit/php-text-template (5.0.1)  - Downloading phpunit/php-invoker (6.0.0)  - Downloading phpunit/php-file-iterator (6.0.2)  - Downloading theseer/tokenizer (2.0.1)  - Downloading sebastian/lines-of-code (4.0.1)  - Downloading sebastian/complexity (5.0.0)  - Downloading phpunit/php-code-coverage (12.5.8)  - Downloading phar-io/version (3.2.1)  - Downloading phar-io/manifest (2.0.4)  - Downloading myclabs/deep-copy (1.14.0)  - Downloading phpunit/phpunit (12.5.40)   0/101 [>---------------------------]   0%   4/101 [=>--------------------------]   3%  11/101 [===>------------------------]  10%  27/101 [=======>--------------------]  26%  37/101 [==========>-----------------]  36%  41/101 [===========>----------------]  40%  51/101 [==============>-------------]  50%  62/101 [=================>----------]  61%  74/101 [====================>-------]  73%  86/101 [=======================>----]  85%  91/101 [=========================>--]  90% 101/101 [============================] 100%  - Installing doctrine/inflector (2.1.0): Extracting archive  - Installing doctrine/lexer (3.0.3): Extracting archive  - Installing dragonmantank/cron-expression (v3.6.0): Extracting archive  - Installing symfony/deprecation-contracts (v3.7.1): Extracting archive  - Installing psr/container (2.0.2): Extracting archive  - Installing fakerphp/faker (v1.24.1): Extracting archive  - Installing symfony/polyfill-mbstring (v1.43.0): Extracting archive  - Installing symfony/http-foundation (v7.4.21): Extracting archive  - Installing fruitcake/php-cors (v1.4.0): Extracting archive  - Installing symfony/polyfill-php82 (v1.43.0): Extracting archive  - Installing symfony/polyfill-php80 (v1.43.0): Extracting archive  - Installing psr/http-message (2.0): Extracting archive  - Installing psr/http-factory (1.1.0): Extracting archive  - Installing psr/http-client (1.0.3): Extracting archive  - Installing guzzlehttp/psr7 (3.1.0): Extracting archive  - Installing guzzlehttp/promises (3.0.2): Extracting archive  - Installing guzzlehttp/guzzle (8.2.0): Extracting archive  - Installing guzzlehttp/uri-template (v2.0.1): Extracting archive  - Installing symfony/polyfill-intl-normalizer (v1.43.0): Extracting archive  - Installing symfony/polyfill-intl-grapheme (v1.43.0): Extracting archive  - Installing symfony/polyfill-ctype (v1.37.0): Extracting archive  - Installing symfony/string (v7.4.21): Extracting archive  - Installing symfony/service-contracts (v3.7.3): Extracting archive  - Installing symfony/console (v7.4.21): Extracting archive  - Installing nunomaduro/termwind (v2.4.0): Extracting archive  - Installing voku/portable-ascii (2.1.1): Extracting archive  - Installing phpoption/phpoption (1.10.0): Extracting archive  - Installing graham-campbell/result-type (v1.2.0): Extracting archive  - Installing vlucas/phpdotenv (v5.7.0): Extracting archive  - Installing symfony/css-selector (v7.4.18): Extracting archive  - Installing tijsverkoyen/css-to-inline-styles (v2.4.0): Extracting archive  - Installing symfony/var-dumper (v7.4.21): Extracting archive  - Installing symfony/polyfill-uuid (v1.43.0): Extracting archive  - Installing symfony/uid (v7.4.21): Extracting archive  - Installing symfony/routing (v7.4.21): Extracting archive  - Installing symfony/process (v7.4.21): Extracting archive  - Installing symfony/polyfill-php86 (v1.43.0): Extracting archive  - Installing symfony/polyfill-php85 (v1.43.0): Extracting archive  - Installing symfony/polyfill-php84 (v1.43.0): Extracting archive  - Installing symfony/polyfill-intl-idn (v1.43.0): Extracting archive  - Installing symfony/mime (v7.4.19): Extracting archive  - Installing psr/event-dispatcher (1.0.0): Extracting archive  - Installing symfony/event-dispatcher-contracts (v3.7.1): Extracting archive  - Installing symfony/event-dispatcher (v7.4.17): Extracting archive  - Installing psr/log (3.0.2): Extracting archive  - Installing egulias/email-validator (4.0.4): Extracting archive  - Installing symfony/mailer (v7.4.19): Extracting archive  - Installing symfony/error-handler (v7.4.20): Extracting archive  - Installing symfony/http-kernel (v7.4.21): Extracting archive  - Installing symfony/finder (v7.4.20): Extracting archive  - Installing ramsey/collection (2.1.1): Extracting archive  - Installing brick/math (1.0.0): Extracting archive  - Installing ramsey/uuid (4.9.4): Extracting archive  - Installing psr/simple-cache (3.0.0): Extracting archive  - Installing symfony/translation-contracts (v3.7.1): Extracting archive  - Installing symfony/translation (v7.4.17): Extracting archive  - Installing symfony/polyfill-php83 (v1.43.0): Extracting archive  - Installing psr/clock (1.0.0): Extracting archive  - Installing symfony/clock (v7.4.21): Extracting archive  - Installing carbonphp/carbon-doctrine-types (3.2.1): Extracting archive  - Installing nesbot/carbon (3.14.2): Extracting archive  - Installing monolog/monolog (3.12.1): Extracting archive  - Installing league/uri-interfaces (7.9.0): Extracting archive  - Installing league/uri (7.9.0): Extracting archive  - Installing league/mime-type-detection (1.17.0): Extracting archive  - Installing league/flysystem-local (3.35.3): Extracting archive  - Installing league/flysystem (3.36.0): Extracting archive  - Installing nette/utils (v4.1.5): Extracting archive  - Installing nette/schema (v1.3.6): Extracting archive  - Installing dflydev/dot-access-data (v3.0.3): Extracting archive  - Installing league/config (v1.2.0): Extracting archive  - Installing league/commonmark (2.10.3): Extracting archive  - Installing laravel/serializable-closure (v2.1.0): Extracting archive  - Installing laravel/prompts (v0.3.26): Extracting archive  - Installing laravel/framework (v13.37.0): Extracting archive  - Installing laravel/pail (v1.2.7): Extracting archive  - Installing laravel/agent-detector (v2.0.4): Extracting archive  - Installing laravel/pao (v1.1.5): Extracting archive  - Installing laravel/pint (v1.32.1): Extracting archive  - Installing nikic/php-parser (v5.9.0): Extracting archive  - Installing psy/psysh (v0.12.24): Extracting archive  - Installing laravel/tinker (v3.0.2): Extracting archive  - Installing hamcrest/hamcrest-php (v3.0.0): Extracting archive  - Installing mockery/mockery (1.6.15): Extracting archive  - Installing filp/whoops (2.18.6): Extracting archive  - Installing nunomaduro/collision (v8.9.5): Extracting archive  - Installing staabm/side-effects-detector (1.0.5): Extracting archive  - Installing sebastian/version (6.0.1): Extracting archive  - Installing sebastian/type (6.0.4): Extracting archive  - Installing sebastian/recursion-context (7.0.1): Extracting archive  - Installing sebastian/object-reflector (5.0.0): Extracting archive  - Installing sebastian/object-enumerator (7.0.0): Extracting archive  - Installing sebastian/global-state (8.0.3): Extracting archive  - Installing sebastian/exporter (7.0.3): Extracting archive  - Installing sebastian/environment (8.1.2): Extracting archive  - Installing sebastian/diff (7.0.1): Extracting archive  - Installing sebastian/comparator (7.1.8): Extracting archive  - Installing sebastian/cli-parser (4.2.1): Extracting archive  - Installing phpunit/php-timer (8.0.0): Extracting archive  - Installing phpunit/php-text-template (5.0.1): Extracting archive  - Installing phpunit/php-invoker (6.0.0): Extracting archive  - Installing phpunit/php-file-iterator (6.0.2): Extracting archive  - Installing theseer/tokenizer (2.0.1): Extracting archive  - Installing sebastian/lines-of-code (4.0.1): Extracting archive  - Installing sebastian/complexity (5.0.0): Extracting archive  - Installing phpunit/php-code-coverage (12.5.8): Extracting archive  - Installing phar-io/version (3.2.1): Extracting archive  - Installing phar-io/manifest (2.0.4): Extracting archive  - Installing myclabs/deep-copy (1.14.0): Extracting archive  - Installing phpunit/phpunit (12.5.40): Extracting archive   0/110 [>---------------------------]   0%  24/110 [======>---------------------]  21%  36/110 [=========>------------------]  32%  51/110 [============>---------------]  46%  63/110 [================>-----------]  57%  76/110 [===================>--------]  69%  87/110 [======================>-----]  79% 101/110 [=========================>--]  91% 110/110 [============================] 100%54 package suggestions were added by new dependencies, use `composer suggest` to see details.Generating optimized autoload files> Illuminate\Foundation\ComposerScripts::postAutoloadDump> @php artisan package:discover --ansi   INFO  Discovering packages.    laravel/pail .......................................................... DONE  laravel/pao ........................................................... DONE  laravel/tinker ........................................................ DONE  nesbot/carbon ......................................................... DONE  nunomaduro/collision .................................................. DONE  nunomaduro/termwind ................................................... DONE80 packages you are using are looking for funding.Use the `composer fund` command to find out more!> @php artisan vendor:publish --tag=laravel-assets --ansi --force   INFO  No publishable resources for tag [laravel-assets].  No security vulnerability advisories found.> @php artisan key:generate --ansi   INFO  Application key set successfully.  > @php -r "file_exists('database/database.sqlite') || touch('database/database.sqlite');"> @php artisan migrate --graceful --ansi   INFO  Preparing database.    Creating migration table ...................................... 25.27ms DONE   INFO  Running migrations.    0001_01_01_000000_create_users_table .......................... 82.52ms DONE  0001_01_01_000001_create_cache_table .......................... 54.00ms DONE  0001_01_01_000002_create_jobs_table ........................... 78.40ms DONE
Создаём приложение Laravel — Главная команда урока: скачивается каркас и все зависимости.
Разбор команды

Главная команда урока: «возьми каркас laravel/laravel и создай из него проект в папке project-laravel». Одна команда — и на диске приложение со всеми зависимостями, ключом и базой.

composer create-project laravel/laravel project-laravel
ТокенЗачем он и что меняет
composerпакетный менеджер PHP; работает от student, без sudo — всё создаётся внутри ~/recoursia.
create-projectсубкоманда «создать проект из пакета»: скачать пакет, распаковать в новый каталог, поставить его зависимости и выполнить post-скрипты.
laravel/laravelимя пакета-каркаса в формате издатель/имя: папки и файлы пустого приложения, у всех одинаковые. Сам фреймворк — отдельный пакет laravel/framework.
project-laravelимя каталога, который будет создан; на момент запуска такого каталога быть не должно.

Что произойдёт

За 19,4 с создано приложение: каркас laravel/laravel v13.11.0 скачан и распакован в /home/student/recoursia/project-laravel, зафиксировано и установлено 110 пакетов (в том числе laravel/framework v13.37.0), сгенерирован автозагрузчик и выполнены скрипты каркаса. Вывод — 391 строка, из них 102 строки - Downloading … и 21 строка прогресса: кэш Composer был пуст, пакеты скачивались заново.

На что смотреть

Строки-вехи по порядку: Creating a "laravel/laravel" project at "./project-laravel" (1) → Installing laravel/laravel (v13.11.0) (2) → Created project in /home/student/recoursia/project-laravel (5) → > @php -r "file_exists('.env') || copy('.env.example', '.env');" (6) → Lock file operations: 110 installs, 0 updates, 0 removals (9) → - Locking laravel/framework (v13.37.0) (27) → Writing lock file (120) → Package operations: 110 installs, 0 updates, 0 removals (122) → - Installing laravel/framework (v13.37.0): Extracting archive (310) → Generating optimized autoload files (356) → INFO Discovering packages. (360) → No security vulnerability advisories found. (375) → INFO Application key set successfully. (378) → создание database/database.sqlite (380) → > @php artisan migrate --graceful --ansi (381) → INFO Preparing database. (383) с Creating migration table … 25.27ms DONE (385) → INFO Running migrations. (387) и три миграции: create_users_table 82.52ms (389), create_cache_table 54.00ms (390), create_jobs_table 78.40ms (391).

Частые ошибки

  • Каталог уже существует и не пуст — Composer откажется создавать проект. sudo не нужен: все файлы принадлежат тому, кто запустил команду. На PHP старее 8.3 установка упрётся в требование каркаса "php": "^8.3". Если скрипт key:generate не отработал, приложение позже ответит No application encryption key has been specified. — ключ дописывают командой php artisan key:generate.

Разбор первых строк:

  • Laravel ставится двумя слоями. laravel/laravel (v13.11.0, строка 2) — каркас: папки и файлы пустого приложения, у всех одинаковые. laravel/framework (v13.37.0 — в списке Locking …, строка 27 вывода, под кнопкой «ещё») — сам фреймворк: роутер, Blade, Eloquent, валидация. Каркас требует в composer.json "laravel/framework": "^13.17", а в lock попала актуальная 13.37.0 — тот же механизм диапазона и фиксации, что с Carbon.
  • Created project in /home/student/recoursia/project-laravel — каркас распакован на диск.
  • Строкой ниже — file_exists('.env') || copy('.env.example', '.env'): уже появился файл настроек .env. Это не магия, а скрипт, который Composer запустил автоматически; до скриптов доберёмся на следующем слайде.
  • Lock file operations: 110 installs — зафиксировано 110 пакетов: фреймворк со всеми зависимостями плюс инструменты разработки — PHPUnit для тестов, Pint для стиля кода, faker для тестовых данных.
Extracting archivecopyLock file operations110 installslaravel/laravel — каркас v13.11.0laravel/laravelПроект создан — ~/recoursia/project-laravelПроектсоздан.env — копия .env.example.envcomposer.lock — 110 installscomposer.lockvendor/ — Downloading → Installingvendor/Автозагрузка — Generating optimized autoload filesАвтозагрузкаExtracting archivecopyLock file operations110 installslaravel/laravel — каркас v13.11.0laravel/laravelПроект создан — ~/recoursia/project-laravelПроектсоздан.env — копия .env.example.envcomposer.lock — 110 installscomposer.lockvendor/ — Downloading → Installingvendor/Автозагрузка — Generating optimized autoload filesАвтозагрузка
Что делает create-project — каркас → проект → lock → vendor → автозагрузка

Команда целиком заняла около 19 секунд — тоже видно в шапке терминала.

18листайте
Часть 1 · Установка Laravel

Что команда сделала сама

Хвост того же вывода (строки 356+, под кнопкой «ещё») — работа скриптов, прописанных в composer.json каркаса; сами скрипты увидим в разборе composer.json. Приложение не просто распаковали — его подготовили к запуску. Пост-скрипты выполнились в таком порядке:

package:discover — pail, tinker, carbon, collision…package:discoverpail, tinker, carbon,collision…key:generate — Application key set successfully.key:generateApplication key setsuccessfully.database.sqlite — база «по умолчанию»database.sqliteбаза «по умолчанию»artisan migrate — 3 миграции + журналartisan migrate3 миграции + журналpackage:discover — pail, tinker, carbon, collision…package:discoverkey:generate — Application key set successfully.key:generatedatabase.sqlite — база «по умолчанию»database.sqliteartisan migrate — 3 миграции + журналartisanmigrate
Пост-скрипты create-project в порядке вывода
  • > @php artisan key:generate --ansi — сгенерировал случайный ключ шифрования и записал его в .env как APP_KEY. Что он шифрует и как выглядит проект без ключа — в части 6.
  • > @php artisan migrate --graceful --ansi — применил к этой базе миграции: Creating migration table ... 25.27ms DONE (журнал применённых миграций) и три миграции каркаса — create_users_table (82.52 мс), create_cache_table (54.00 мс), create_jobs_table (78.40 мс): таблицы пользователей, кэша и очередей задач.
Что такое миграции (анонс части 4)

В блоке «Базы данных» вы рисовали схему руками: таблицы в PHPMyAdmin, колонки через ALTER. В Laravel схема — это код: файлы-миграции с версиями, которые применяет artisan migrate. Поэтому только что созданное приложение уже имеет базу с таблицами: их никто не рисовал руками — на любой другой машине та же команда воспроизведёт их в точности.

19листайте
Часть 1 · Установка Laravel

Сколько это весит и почему vendor/ не коммитят

student@mint: ~/recoursia/project-laravel751 мс
student@mint:~/recoursia/project-laravel$
Сколько весит проект
Разбор команды

Меряем, сколько весит установленный проект и сколько в нём пакетов, — цифры для разговора о том, почему vendor/ не коммитят в git.

du -sh . vendor
ТокенЗачем он и что меняет
duсчитать место, занятое на диске.
-shдва флага слитно: -s — итог по каждому аргументу, без подкаталогов, -h — человекочитаемые единицы.
.текущий каталог — проект целиком.
vendorвторой аргумент — каталог с пакетами.

Что произойдёт

Одна строка: 76M на весь проект. Отдельной строки для vendor нет — du считает каждый файл один раз, а vendor уже обойдён внутри ..

На что смотреть

76 МБ — это почти весь вес vendor/, чужого кода из lock-файла. Свои файлы на этом фоне невесомы: artisan — 425 байт, composer.json — 2 678.

Частые ошибки

  • Нужен размер vendor отдельно — запустите du -sh vendor одной командой: в общем вызове он попадёт в already-counted и строки не даст. du меряет блоки, поэтому его число чуть больше суммы размеров из ls -l.
echo "каталогов в vendor: $(find vendor -maxdepth 1 -mindepth 1 -type d | wc -l)"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки: внутри них shell выполняет $(…) и подставляет результат.
$(…)подстановка команды — вывод конвейера становится частью строки.
find vendorобойти каталог vendor.
-maxdepth 1не спускаться глубже первого уровня.
-mindepth 1не печатать сам vendor — только его детей.
-type dотбирать только каталоги, без файлов вроде autoload.php.
|пайп: передать список следующей команде.
wc -lпосчитать строки, то есть количество каталогов.

Что произойдёт

каталогов в vendor: 36 — столько каталогов верхнего уровня лежит в vendor/.

На что смотреть

36 каталогов против 110 пакетов: пакеты вида symfony/console живут внутри каталога издателя symfony/, поэтому каталогов меньше, чем пакетов.

Частые ошибки

  • Без -mindepth 1 в счёт попал бы сам vendor и получилось 37. Флаги глубины у find пишут до выражений (-type, -name) — иначе find предупреждает, что проверяет их не в том порядке.
echo "пакетов установлено: $(composer show -i | wc -l)"
ТокенЗачем он и что меняет
echoпечатает строку.
"…"двойные кавычки для подстановки $(…).
composer showпоказать установленные пакеты — по одному на строку.
-iто же, что --installed: флаг устарел, установленные пакеты показываются по умолчанию — отсюда предупреждение в выводе.
|пайп: список пакетов уходит в wc -l.
wc -lпосчитать строки.

Что произойдёт

пакетов установлено: 110 — плюс ворчание Composer: You are using the deprecated option "installed". Only installed packages are shown by default now.

На что смотреть

110 совпадает со строкой Lock file operations: 110 installs из вывода create-project. Предупреждение про -i уходит в stderr и на число не влияет.

Частые ошибки

  • Запуск composer show вне проекта (без composer.json и vendor/) считает не те пакеты: число перестанет относиться к вашему приложению.

Числа: 76 МБ весь проект, 36 каталогов верхнего уровня внутри vendor/, 110 установленных пакетов. (Строка You are using the deprecated option "installed" — ворчание Composer на флаг -i у composer show; на числа она не влияет.)

Почему 76 МБ — не страшно: почти весь вес — vendor/, чужой код, установленный из lock-файла. Правило из демо-папки работает здесь один в один: в git живут composer.json и composer.lock, а vendor/ — нет; клонировал репозиторий, запустил composer install — получил те же 110 пакетов тех же версий.

Собственный код на этом фоне невесом: посмотрите на ls из следующего слайда — artisan занимает 425 байт, composer.json — 2 678 байт. Папки app/, routes/, resources/ после установки почти пустые — их мы начнём заполнять со второй части.

20листайте
Часть 1 · Анатомия проекта

Корень проекта: первый осмотр

student@mint: ~/recoursia/project-laravel456 мс
student@mint:~/recoursia/project-laravel$
Что лежит в корне проекта
Разбор команды

Первый осмотр корня свежеустановленного проекта: что команда установки оставила на диске.

ls -la
ТокенЗачем он и что меняет
lsсписок файлов текущего каталога — мы стоим в корне проекта.
-laдва флага слитно: -l — длинный формат (права, владелец, размер, дата изменения), -a — показывать скрытые записи: без него не видно ни .env, ни .env.example, ни .gitignore.

Что произойдёт

total 412 и 25 записей: 10 каталогов (app, bootstrap, config, database, public, resources, routes, storage, tests, vendor) и 15 файлов, включая шесть скрытых.

На что смотреть

Размеры из вывода: .env — 1 142 байта, .env.example — 1 091 (живые настройки и их шаблон); artisan — 425 байт с правами -rwxr-xr-x, то есть исполняемый; composer.json — 2 678 байт против composer.lock — 313 898. Даты делят записи на две группы: Sep 30 13:56 — файлы из архива каркаса, Oct 17 10:19 — то, что создала установка (.env, composer.lock, database/, vendor/).

Частые ошибки

  • ls без -a — главная причина «у меня нет .env»: файл на месте, просто скрытый. Число 38 в строке vendor — счётчик ссылок каталога: 36 подкаталогов плюс . и ...

Что важно заметить уже сейчас (детальный обход каждого каталога — часть 2):

  • .env (1 142 байта) и .env.example (1 091 байт) — настройки и их шаблон; обсудим отдельно ниже.
  • .gitignore — список того, что не попадает в git; в проекте Laravel там vendor/ и .env.
  • artisan (425 байт, исполняемый -rwxr-xr-x) — консоль приложения; ей — два слайда в конце части.
  • composer.json (2 678 байт) и composer.lock (313 898 байт — точные версии всех 110 пакетов) — знакомые по демо роли: «хотим» и «реально стоит».
  • vendor/ — 36 каталогов чужого кода; в git не живёт.
  • Каталоги app/, bootstrap/, config/, database/, public/, resources/, routes/, storage/, tests/ — каркас MVC; на следующем слайде — что где.
  • package.json и vite.config.js — сборщик фронтенда Vite. В этом уроке мы его не включаем: стили положим обычным CSS-файлом в public/ (часть 3).
  • phpunit.xml и tests/ — тесты; доберёмся в части 6.
21листайте
Часть 1 · Анатомия проекта

Структура: что где лежит

Тот же корень деревом. Команда tree с флагом -I исключила vendor/, node_modules/ и .git/ — они видны в командной строке терминала:

student@mint: ~/recoursia/project-laravel389 мс
student@mint:~/recoursia/project-laravel$
├── .editorconfig├── .env├── .env.example├── .gitattributes├── .gitignore├── .npmrc├── AGENTS.md├── CLAUDE.md├── README.md├── artisan├── composer.json├── composer.lock├── package.json├── phpunit.xml└── vite.config.js25 directories, 36 files
Структура каталогов
Разбор команды

Тот же корень — деревом и без vendor/: так видна структура MVC, а не 110 установленных пакетов.

tree -L 2 -a -I 'vendor|node_modules|.git' --dirsfirst
ТокенЗачем он и что меняет
treeрекурсивный список в виде дерева; ставится отдельным пакетом, в базовой системе его может не быть.
-L 2ограничить глубину двумя уровнями: корень и его дети; без флага tree обошёл бы все вложенные каталоги.
-aвключать скрытые записи — .env, .gitignore, database/.gitignore.
-I 'vendor|node_modules|.git'исключить каталоги по шаблону. | здесь — «или» внутри шаблона tree, а не пайп shell; кавычки обязательны, иначе shell разорвал бы команду на части.
--dirsfirstсначала каталоги, потом файлы — структуру читаете сверху вниз.

Что произойдёт

63 строки: девять каталогов каркаса с содержимым, файлы корня и итог 25 directories, 36 files.

На что смотреть

Итоговая строка — это «ваш» проект без vendor/. Сверьте с таблицей урока: в config/ ровно 10 файлов, в routes/ — web.php и console.php, в database/ уже лежит database.sqlite, созданный при установке, а public/ содержит index.php — единственную точку входа.

Частые ошибки

  • tree: command not found — пакет не установлен (sudo apt install tree). Забытый -I превращает вывод в простыню из vendor/, за которой структуру не видно.
Каталог Что внутри Когда пригодится
app/ наш код: Http/, Models/, Providers/ части 3–5: контроллеры, модель User
bootstrap/ запуск приложения: app.php, providers.php, cache/ часть 2 — прочитаем один раз
config/ 10 файлов конфигурации: app.php, database.php, session.php… часть 2; править будем редко — настройки живут в .env
database/ migrations/, seeders/, factories/ + database.sqlite часть 4 — миграции и сидеры
public/ index.php — единственная точка входа; favicon.ico, robots.txt часть 2 — root в nginx укажет сюда
resources/ css/, js/, views/ — шаблоны страниц части 2–5 — все шаблоны
routes/ web.php — таблица маршрутов; console.php часть 2 и дальше — почти в каждой части
storage/ logs/, framework/, app/ — что приложение пишет часть 6 — логи и права
tests/ Feature/, Unit/, TestCase.php часть 6

Итоговая строка tree — «25 directories, 36 files» — это без vendor/. В части 2 пройдём по структуре ещё раз, уже читая файлы.

22листайте
Часть 1 · Анатомия проекта

composer.json: зависимости, автозагрузка, скрипты

Знакомый по демо файл — только на 86 строк. Вставляем его прямо из захвата (цифры слева — номера строк); подсвечены: требование к PHP и фреймворку (9–10), правило PSR-4 (24) и скрипты установки (61–64).

composer.jsonjson
1{2 "$schema": "https://getcomposer.org/schema.json",3 "name": "laravel/laravel",4 "type": "project",5 "description": "The skeleton application for the Laravel framework.",6 "keywords": ["laravel", "framework"],7 "license": "MIT",8 "require": {9 "php": "^8.3",10 "laravel/framework": "^13.17",11 "laravel/tinker": "^3.0"12 },13 "require-dev": {14 "fakerphp/faker": "^1.23",15 "laravel/pail": "^1.2.5",16 "laravel/pao": "^1.0.6",17 "laravel/pint": "^1.27",18 "mockery/mockery": "^1.6",19 "nunomaduro/collision": "^8.6",20 "phpunit/phpunit": "^12.5.12"21 },22 "autoload": {23 "psr-4": {24 "App\\": "app/",25 "Database\\Factories\\": "database/factories/",26 "Database\\Seeders\\": "database/seeders/"27 }28 },29 "autoload-dev": {30 "psr-4": {31 "Tests\\": "tests/"32 }33 },34 "scripts": {35 "setup": [36 "composer install",37 "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",38 "@php artisan key:generate",39 "@php artisan migrate --force",40 "npm install --ignore-scripts",41 "npm run build"42 ],43 "dev": [44 "Composer\\Config::disableProcessTimeout",45 "@php artisan dev"46 ],47 "test": [48 "@php artisan config:clear --ansi @no_additional_args",49 "@php artisan test"50 ],51 "post-autoload-dump": [52 "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",53 "@php artisan package:discover --ansi"54 ],55 "post-update-cmd": [56 "@php artisan vendor:publish --tag=laravel-assets --ansi --force"57 ],58 "post-root-package-install": [59 "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""60 ],61 "post-create-project-cmd": [62 "@php artisan key:generate --ansi",63 "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",64 "@php artisan migrate --graceful --ansi"65 ],66 "pre-package-uninstall": [67 "Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"68 ]69 },70 "extra": {71 "laravel": {72 "dont-discover": []73 }74 },75 "config": {76 "optimize-autoloader": true,77 "preferred-install": "dist",78 "sort-packages": true,79 "allow-plugins": {80 "pestphp/pest-plugin": true,81 "php-http/discovery": true82 }83 },84 "minimum-stability": "stable",85 "prefer-stable": true86}

Что нужно проекту

  • "php": "^8.3" (строка 9) — то самое требование из начала части: PHP 8.3+. Именно эта строка не дала бы каркасу установиться на старый PHP.
  • "laravel/framework": "^13.17" (строка 10) — фреймворк. Указан диапазон, а в lock попала 13.37.0 — мы видели это в выводе create-project. Та же механика, что ^3.14 у Carbon.
  • "laravel/tinker" — консольный REPL внутри приложения: в части 4 будем создавать в нём записи и смотреть SQL, который получается у Eloquent.
  • require-dev — пакеты только для разработки: PHPUnit (тесты, часть 6), faker (случайные данные для сидеров, часть 4), pint (проверка стиля кода), collision и pail (читаемые ошибки и логи в консоли), mockery (подмена объектов в тестах). На боевой сервер они не поедут — в части 6 поставим пакеты с флагом --no-dev.

Автозагрузка и скрипты

  • "App\\": "app/" (строка 24) — та самая PSR-4 из демо, теперь для нашего кода: класс App\Models\User будет лежать в app/Models/User.php, App\Http\Controllers\UserController — в app/Http/Controllers/UserController.php. Достаточно создать класс в app/ — ни одного require. Тесты Tests\ подключаются отдельно, через autoload-dev.
  • post-create-project-cmd (строки 61–64) — выполняется один раз при создании проекта: сгенерировать ключ → создать файл SQLite → применить миграции. Ровно эти три действия, в этом порядке, мы видели в хвосте create-project.
  • post-autoload-dump — обнаружение пакетов после генерации автозагрузчика: строка INFO Discovering packages. из вывода — его работа.
  • setup — рецепт первого запуска целиком: composer install, копия .env, ключ, миграции, сборка фронтенда через npm.
23листайте
Часть 1 · Анатомия проекта

.env: настройки вне кода

В блоке «Базы данных» подключение к PDO — DSN, логин, пароль — было записано прямо в PHP-файле. Работает, но приваривает настройки и секреты к коду: другая база на боевом сервере — правь исходники. Laravel выносит всё это в файл .env. Он перед вами целиком, из захвата; подсвечены ключ и отладка (3–4), база (23) и сессии (30):

.envini
1APP_NAME=Laravel2APP_ENV=local3APP_KEY=base64:9yll+jWAAsG/GlhU7PjAxvfYR/iUgFc1EUlX6rWinHo=4APP_DEBUG=true5APP_URL=http://localhost:800067APP_LOCALE=en8APP_FALLBACK_LOCALE=en9APP_FAKER_LOCALE=en_US1011APP_MAINTENANCE_DRIVER=file12# APP_MAINTENANCE_STORE=database1314# PHP_CLI_SERVER_WORKERS=41516BCRYPT_ROUNDS=121718LOG_CHANNEL=stack19LOG_STACK=single20LOG_DEPRECATIONS_CHANNEL=null21LOG_LEVEL=debug2223DB_CONNECTION=sqlite24# DB_HOST=127.0.0.125# DB_PORT=330626# DB_DATABASE=laravel27# DB_USERNAME=root28# DB_PASSWORD=2930SESSION_DRIVER=database31SESSION_LIFETIME=12032SESSION_ENCRYPT=false33SESSION_PATH=/34SESSION_DOMAIN=null3536BROADCAST_CONNECTION=log37FILESYSTEM_DISK=local38QUEUE_CONNECTION=database3940CACHE_STORE=database41# CACHE_PREFIX=4243MEMCACHED_HOST=127.0.0.14445REDIS_CLIENT=phpredis46REDIS_HOST=127.0.0.147REDIS_PASSWORD=null48REDIS_PORT=63794950MAIL_MAILER=log51MAIL_SCHEME=null52MAIL_HOST=127.0.0.153MAIL_PORT=252554MAIL_USERNAME=null55MAIL_PASSWORD=null56MAIL_FROM_ADDRESS="hello@example.com"57MAIL_FROM_NAME="${APP_NAME}"5859AWS_ACCESS_KEY_ID=60AWS_SECRET_ACCESS_KEY=61AWS_DEFAULT_REGION=us-east-162AWS_BUCKET=63AWS_USE_PATH_STYLE_ENDPOINT=false6465VITE_APP_NAME="${APP_NAME}"
  • APP_KEY (строка 3) — тот самый ключ, который key:generate создал при установке. Он шифрует то, что не должны читать посторонние: cookie и данные сессий. Как выглядит приложение без ключа — часть 6. Ключ на слайде — от локального учебного приложения; настоящий .env не показывают никому.
  • APP_DEBUG=true (строка 4) — подробные ошибки прямо в браузере. В разработке это помощь, на бою — утечка путей и настроек; там ставят false. Прямой наследник display_errors из php.ini: та же философия — в разработке показываем, на бою молчим.
  • APP_ENV=local (строка 2) — «это машина разработки»; APP_URL — адрес, которым приложение себя считает.
  • DB_CONNECTION=sqlite (строка 23) — база по умолчанию, тот самый файл database/database.sqlite. Строки 24–28 — закомментированные готовые настройки MySQL/MariaDB: хост, порт, база, пользователь, пароль. В части 4 раскомментируем и заполним.
  • SESSION_DRIVER=database (строка 30) — сессии хранятся в базе (для них, в том числе, бежали миграции при установке), а не в файлах.
.env не коммитят в репозиторий

В нём настоящие ключи и пароли. В git живёт шаблон .env.example без секретов — мы видели в ls оба файла, 1 142 и 1 091 байт. При разворачивании проекта .env создают из шаблона и заполняют — ровно это и сделал первый скрипт create-project: file_exists('.env') || copy('.env.example', '.env').

24листайте
Часть 1 · artisan

artisan — консоль Laravel

У проекта есть собственная точка входа для командной строки — исполняемый файл artisan в корне (425 байт, права -rwxr-xr-x из ls). Идея вам знакома по Linux: systemctl — единая точка управления всеми сервисами; artisan — единая точка для всего, что делается вокруг приложения.

student@mint: ~/recoursia/project-laravel751 мс
student@mint:~/recoursia/project-laravel$
Artisan — консоль Laravel
Разбор команды

У проекта своя точка входа для консоли — файл artisan в корне. Проверяем, что он запускается и какой фреймворк под ним.

php artisan --version
ТокенЗачем он и что меняет
phpинтерпретатор CLI — он выполняет файл artisan.
artisanскрипт в корне проекта (425 байт, права -rwxr-xr-x): единая точка для всего, что делается вокруг приложения.
--versionнапечатать версию фреймворка и выйти, не выполняя команду приложения.

Что произойдёт

Laravel Framework 13.37.0.

На что смотреть

Та же версия, что в lock-файле и в выводе create-project: каркас laravel/laravel и фреймворк laravel/framework — разные пакеты, и версия здесь — про фреймворк. php artisan без аргументов печатает список всех доступных команд.

Частые ошибки

  • Could not open input file: artisan — вы не в корне проекта. Файл исполняемый, поэтому работает и ./artisan --version, но вариант php artisan … не зависит от прав на исполнение.

php artisan --version печатает версию фреймворка — 13.37.0: та же, что в lock-файле и в выводе create-project.

Команды, которые понадобятся в уроке (запоминать не нужно — каждая встретится не раз):

  • php artisan serve — dev-сервер, чтобы сразу открыть проект в браузере (часть 2);
  • php artisan about — сводка о приложении (следующий слайд);
  • php artisan make:controller, make:migration, make:seeder — генераторы: создают файл по правильному пути и с правильной заготовкой (части 3–5);
  • php artisan migrate — применить миграции к базе (часть 4);
  • php artisan tinker — REPL внутри приложения (часть 4).
25листайте
Часть 1 · artisan

php artisan about — приборная панель

Одна команда — и вся правда о приложении на одном экране:

student@mint: ~/recoursia/project-laravel894 мс
student@mint:~/recoursia/project-laravel$
Сводка об окружении приложения — Одна команда — вся правда о версии, драйверах и настройках.
Разбор команды

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

php artisan about
ТокенЗачем он и что меняет
phpинтерпретатор CLI.
artisanконсоль проекта.
aboutкоманда-сводка: собирает значения из конфигов и .env и печатает их блоками.

Что произойдёт

30 строк в четырёх блоках: Environment, Cache, Drivers, Storage.

На что смотреть

Блок Environment: Laravel Version 13.37.0, PHP Version 8.3.6, Composer Version 2.7.1 — всё сходится с предыдущими захватами; Environment local, Debug Mode ENABLED (это APP_DEBUG=true из .env), URL localhost:8000 (это APP_URL), Maintenance Mode OFF, Timezone UTC, Locale en. Блок Cache: Config, Events, Routes и Views — все NOT CACHED, то есть конфиги, события, маршруты и шаблоны читаются с диска на каждом запросе, и правки действуют сразу. Блок Drivers: Database sqlite, Session database, Cache database, Queue database, Broadcasting log, Mail log, Logs stack / single. Блок Storage: public/storage — NOT LINKED.

Частые ошибки

  • Поле URL — это значение APP_URL из .env, а не адрес, по которому сайт реально отвечает: в части 2 приложение откроется по локальному домену зоны .rs. Debug Mode ENABLED в разработке помогает, а на бою показывает посторонним пути и настройки — там его выключают.
  • Environment: Laravel 13.37.0, PHP 8.3.6, Composer 2.7.1 — всё сходится с захватами выше. Окружение local, Debug Mode ENABLED — это APP_DEBUG=true из .env. Часовой пояс UTC, локаль en.
  • Cache: везде NOT CACHED — конфигурация, события, маршруты и шаблоны читаются с диска на каждом запросе. Медленнее, зато правка любого файла действует сразу — то, что нужно в разработке. Когда кэш включать и как сбрасывать — часть 6.
  • Drivers: где приложение хранит данные. Database: sqlite — файл, созданный при установке. Session, Cache, Queue: database — таблицы из тех самых миграций. Mail: log — письма не отправляются, а пишутся в лог: в разработке можно «отправить» письмо и прочитать его файлом. Logs: stack / single.
  • Storage: public/storage NOT LINKED — символическая ссылка для файлов, загруженных пользователями, не создана. В этом уроке не понадобится.
Привычка

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

Версия PHP

8.3 и php8.3 в командах выше — версия стенда. Проверьте свою: php -v. Если она другая — замените 8.3 на свою во всех путях (/etc/php/…, /run/php/…), именах пакетов (php8.3-*) и сервисе (php8.3-fpm).

26листайте
Часть 1 · Итоги

Итог части: что и где лежит

Composer 2.7.1 — от пользователя student, без sudoComposer 2.7.1от пользователяstudent, без sudoproject-laravel — каркас laravel/laravel v13.11.0project-laravelкаркасlaravel/laravelv13.11.0vendor/ — фреймворк 13.37.0 · 110 пакетов · 36 каталоговvendor/фреймворк 13.37.0 ·110 пакетов · 36каталоговcomposer-demo — 9 пакетов · vendor 7.2 МБ · PSR-4composer-demo9 пакетов · vendor7.2 МБ · PSR-4.env — настройки и APP_KEY, копия .env.example.envнастройки и APP_KEY,копия .env.exampledatabase.sqlite — 3 миграции: users, cache, jobsdatabase.sqlite3 миграции: users,cache, jobscreate-projectrequire110 installscopy + key:generatetouch + migratecreate-projectrequire110 installscopy + key:generatetouch + migrateComposer 2.7.1 — от пользователя student, без sudoComposer2.7.1project-laravel — каркас laravel/laravel v13.11.0project-laravelvendor/ — фреймворк 13.37.0 · 110 пакетов · 36 каталоговvendor/composer-demo — 9 пакетов · vendor 7.2 МБ · PSR-4composer-demo.env — настройки и APP_KEY, копия .env.example.envdatabase.sqlite — 3 миграции: users, cache, jobsdatabase.sqlite
Итог части — две папки в ~/recoursia и что внутри

project-laravel — приложение, которое построим в частях 2–6, а vendor/ внутри него — роутинг, шаблоны, ORM и валидация: то, что больше не пишем руками (76 МБ на весь проект). composer-demo — учебный эксперимент первой половины части.

Системно за часть мы не поставили ничего: весь стек — PHP, nginx, MariaDB, dnsmasq, Composer — уже стоял из блока Linux, а оба проекта собрались внутри пользовательских папок, без sudo.

27листайте
Часть 1 · Проверьте себя

Проверьте себя

  1. Мы попросили у Composer один пакет nesbot/carbon, а установилось девять. Почему? И в каком файле записаны точные версии всех девяти?
  2. В composer.json написано "nesbot/carbon": "^3.14", а в lock-файле — 3.14.2. Зачем второй файл, если есть первый: какую проблему решает lock?
  3. .env и .env.example — чем они различаются, какой из них попадает в git, а какой появился из него при установке?
  4. Назовите не меньше трёх действий, которые composer create-project выполнил сам, после того как распаковал файлы каркаса.
  5. php artisan about показывает Database: sqlite. Вручную базу никто не создавал — откуда она взялась и какие таблицы в ней появились при установке?
28листайте
Часть 1 · Проверьте себя

Проверьте себя: ответы коротко

  1. Carbon тянет за собой зависимости: symfony/translation, polyfill-mbstring, psr/clock и другие — дерево построил Composer. Точные версии всех девяти — в composer.lock (строки Locking … (3.14.2) и т. д. в выводе require).
  2. ^3.14 — диапазон: «любая 3.x не ниже 3.14». Через месяц свежая установка взяла бы 3.15, хотя вы тестировали 3.14.2. Lock фиксирует точные версии, чтобы коллега, CI и сервер поставили то же самое.
  3. .env — живые настройки с настоящим APP_KEY; в git не коммитят. .env.example — шаблон без секретов; коммитят его. При установке .env создан копией шаблона — первым скриптом create-project.
  4. Скопировал .env из .env.example; сгенерировал и записал APP_KEY (key:generate → INFO Application key set successfully.); создал database/database.sqlite; применил миграции (users, cache, jobs плюс журнал миграций); запустил обнаружение пакетов (package:discover). Достаточно любых трёх.
  5. Её создал тот же create-project: скрипт touch сделал пустой файл SQLite, а artisan migrate построил в нём таблицы — в выводе видно Creating migration table и три миграции: create_users_table, create_cache_table, create_jobs_table.
29листайте
Часть 1 · Что дальше

Что дальше — часть 2

Приложение лежит на диске, но пока это просто файлы. В части 2 оно начнёт отвечать на запросы:

  • прочитаем public/index.php — единственную точку входа — и свяжем её с путём, который вы уже знаете: браузер → DNS → nginx → PHP-FPM → буфер вывода → ответ (lesson.http.i.01);
  • разберём bootstrap/app.php и routes/web.php — где живёт таблица маршрутов;
  • напишем первый маршрут и первый шаблон Blade, откроем их через php artisan serve;
  • настроим настоящий nginx на project-laravel.rs: root в public/, try_files, fastcgi_pass в знакомый сокет — и по дороге соберём две классические ошибки конфигурации с настоящими 403 и 404 в захватах.

Всё, что мы сегодня установили, там начнёт работать.

30листайте