Данил Щуцкий

+46
с 2023

Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel

27 подписчиков
0 подписок
1

Всем привет!

На CutCode мы ежемесячно смотрим новости PHP и Laravel. Самое время поговорить о самом лучшем, самом совершенном, великолепном и бесподобном PHP-фреймворке современности - о Symfony!

Всем привет!
Это дайджест новостей от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP и Laravel.

Давайте посмотрим, что произошло за прошедший месяц в мире PHP.

Долго откладывал, но всё-таки добрался до разбора интервью с Taylor Otwell (далее по тексту T - для сокращения) на YouTube-канале ThePrimeTime. Признаюсь, формат интервью с разработчиками мне казался скучным. Редко когда узнаю что-то полезное, но всё-таки у меня канал и комьюнити посвящены Laravel и просто обязан знать все новости, а также планы T.…

1

Привет, коллеги! 👋

Продолжаем цикл статей, посвященных деплою приложений на сервер.

1

Всем привет!

Это дайджест новостей от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP и Laravel.

Всем привет!

Это PHP Дайджест от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP.

27
1

Напомню, что MoonShine это open-source админ-панель для проектов на Laravel.

Давайте взглянем на самое интересное в этом обновлении!

Всем привет! Это PHP Дайджест от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP.

Новости

2
\n\nAlpine\n\n
\n \n
\n\nBootstrap:\n\n
\n \n
\n Action\n Another action\n Something else here\n
\n
","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.29 Add waitUntil method to Process"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/53236

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У нас уже был процесс с новым методом stop, также появился новый метод waitUntil.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если мы имеем дело с long-running процессом, мы с вами также можем динамически указать сколько времени мы можем ожидать его завершения.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.29 Add helper method to determine stray request prevention state"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/53232

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И напоследок по релизу 11.29 и дайджесту за октябрь — это pull request, который затрагивает HTTP-клиент и добавляет метод preventingStrayRequests. На самом деле, мне кажется, он уже был. Но как бы там ни было, видим, что он добавлен в 11 версию. И тем самым он предостерегает нас, чтобы не было запросов к каким-либо внешним API. Это будет полезно на уровне тестов, чтобы мы были уверены, что у нас все запросы фейковые, а не реальные.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Напоминаю вам, что 30 октября выходит викторина от CutCode \"Своя игра\", уже третий выпуск. Всех вас обязательно ждем в прямом эфире.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Видео-версия дайджеста:

"}},{"type":"video","cover":false,"hidden":false,"anchor":"","data":{"title":"","video":{"type":"video","data":{"thumbnail":{"type":"image","data":{"uuid":"93f12de4-9b26-53be-a10e-5f187d7d7433","width":1280,"height":720,"size":186055,"type":"jpg","color":"1a4658","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQCAwMDAgQDAwMEBAQEBQkGBQUFBQsICAYJDQsNDQ0LDAwOEBQRDg8TDwwMEhgSExUWFxcXDhEZGxkWGhQWFxb/2wBDAQQEBAUFBQoGBgoWDwwPFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhb/wAARCAAKAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABgMF/8QAJRAAAgIBAgUFAQAAAAAAAAAAAQQCAwUAERIiMVJhBhMWISOB/8QAFQEBAQAAAAAAAAAAAAAAAAAABAf/xAAgEQABAwQCAwAAAAAAAAAAAAABAgMEACIxMgUSIUFC/9oADAMBAAIRAxEAPwDVzPptfLqXzqVtUtfsqDqElIw9yNcuICJPUeRq9CmLrohD4KtyxA58fQZf3666D5JlmKqvCxaNqRttM9ui2XfeGVaAdYAF89v1l3Hzo7seHIKe4XaANhgY+arsPgBHaLSHLSpStRlR8+6//9k="}},"width":800,"height":450,"time":0,"external_service":{"name":"youtube","id":"FgZCxFcubfM"}}}}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":0,"reposts":0,"views":317,"hits":65,"reads":null,"online":0},"dateFavorite":0,"hitsCount":65,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1625002-php-i-laravel-daidzhest-novostei-za-oktyabr-2024-goda","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1620989,"customUri":null,"subsiteId":2317176,"title":"Куда движется Laravel? Обзор интервью с Taylor Otwell","date":1730179645,"dateModified":1730180068,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Долго откладывал, но всё-таки добрался до разбора интервью с Taylor Otwell (далее по тексту T - для сокращения) на YouTube-канале ThePrimeTime. Признаюсь, формат интервью с разработчиками мне казался скучным. Редко когда узнаю что-то полезное, но всё-таки у меня канал и комьюнити посвящены Laravel и просто обязан знать все новости, а также планы T. И я не пожалел - несмотря на то что брали интервью его фанаты, и каверзных вопросов не было, интервью было интересным и очень важным. T дал понять, куда движется развитие Laravel и почему был выбран именно этот маршрут. Забегу вперёд - хейтеры Laravel будут очень довольны 😉.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Too dumb for React"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вопрос к T почему изначально выбор пал на Vue в экосистеме Laravel, а не React - на мой взгляд стал \"гвоздём программы\".

"}},{"type":"quote","cover":false,"hidden":false,"anchor":"","data":{"text":"

\"Too dumb for React, enjoying Vue\"

","subline1":"Taylor Otwell"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Приятная самоирония меня улыбнула, но это фраза — основа ответов на многие вопросы. Не смог T понять React и выбрал Vue - потому что Vue проще.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее T признаётся, что Laravel Cloud использует стек Inertia + React. В этом проекте React отлично вписался, так как имеет больший набор готовых компонентов. T всё ещё с React не смог разобраться, но уже достаточно зарабатывает и может позволить себе нанять разработчиков, которые хорошо разбираются в React. Про команду Laravel и то насколько T им доверяет мы ещё обсудим.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Много знать плохо"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

T делится своим отношением к Inertia. Считает что большое преимущество Inertia в том, что она не требует изучения чего-то нового, и является просто мостом между хорошо известными инструментами, и при этом позволяет в разы экономить время на разработку.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Полностью согласен с T. Кстати говоря все проекты из экосистемы CutCode написаны на Laravel + Vue + Inertia и на данный момент приносит только позитивные эмоции. На днях же выйдет Inertia v2, а я давно ничего не снимал по ней на канал. Может пора сделать новый видеогайд по Inertia?!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Собственно здесь и ответ на вопрос, почему T не применяет Livewire или Hotwire😉. T за простоту - чем меньше фреймворков, в которые нужно вникать, тем лучше.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Мы играем хиты для фанатов"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее был интересный вопрос о хейте Laravel, и, в частности, о основных направлениях ненависти, а именно ORM и Queue.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

T сказал, что фанаты любят ORM в том виде, в каком он есть и то же самое про очереди. T прислушивается к фанатам и работает именно для них. Философия Laravel — низкий порог входа и охват большинства. Как вывод - основные хиты рок-группа \"Laravel\" играет для фанатов, а хейтеров даже не пускают на концерт!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Напомню, что ежегодно T проводит опрос в Laravel комьюнити - stateoflaravel.com, где больше 80% респондентов согласны/полностью согласны, что Laravel Framework движется в правильном направлении.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кстати, ORM переписывали три раза и мнение T, что сейчас она идеальна. Нам приходится ему верить. 😅

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Да T считает, что их система очередей очень умная, ведь она умеет сериализовать и гидрировать модели, это прям революция 😉. Но мне показалось немного странным, что сравнивает он её с реализациями в JS фреймворках, а не с той же Symfony или хотя бы Битриксом. 😅

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Дормамму* ты не пройдёшь!"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

*могущественный персонаж-плохиш вселенной Marvel

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Каждый Pull Request лично проверяет T и именно он жмёт кнопку смержить или же закрыть PR. Каждая строчка кода должна быть в стиле Laravel! Для T важно, чтобы исходный код не превратился в кашу стилей от разных разработчиков со своим бэкграундом и поэтому он лично всё перепроверяет. И даже если код работает и выполняет свои задачи, но по стилю и красоте не нравится T, то он его может даже закрыть и сделать самостоятельно, но так как видит именно он.

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"e92ffce4-16f4-5298-9922-7553e770e1f7","width":750,"height":561,"size":204041,"type":"jpg","color":"d3a888","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAQECAQEBAgICAgICAgICAQICAgICAgICAgL/2wBDAQEBAQEBAQEBAQECAQEBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgL/wAARCAAKAAoDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAACQr/xAAiEAACAgMAAgEFAAAAAAAAAAABAgMEBQYHAAgRITEyUWH/xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgX/xAAqEQACAQIFAgQHAAAAAAAAAAABAgMEIQAFBhETEjEUI4PSMjNBUXGxsv/aAAwDAQACEQMRAD8ALf3j92Nhynrv2/iE3Rpc50Ln9nlOwcy2/GUjW3TENsMGo4fY8E264DIrDBRoQ19hhsVoqUda9WvVrBleRHeQfy5tRVWsctzKhqp5dN1QmMyudo1KRFQFjaNWJd+grs7dLCQkXs5ZlS6TotA12XZjSQU2s6UxJEI1bkbkn5DI78rjaOLrDkogIMag7AFp+z2L25JJHZt+YEkhh0dgG+v5ANlgQD/QD+wPFgVKEfLkHpSe3AwaKQE+dCfWi92L86tSpc9NOyZS3Vr2snZ7trUFnI2YY571iGrsWqNVhntyqZJYo2ZjGrMQhYlQPnyepFVYqZFUKgphYDYXVt7dr/XG7USSNm0zs5Z/F9ySTaRQL97Dt9sFjkMPiHv3nfF453e5ZZ3ajWZmZpnLMzGL5ZiSSSfv8+U6fAv4H6xOVBJnnJO5Lt/Rx//Z"}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тут же можно вспомнить начало интервью, про \"too dumb\" и получается, что если T не понимает или ещё не знаком с чем-то новым (а как мы уже поняли, изучать новое он не очень любит), то высока вероятность отказа. Так что забудьте о более глубокой интеграции с Longrunning и выходом в этом вопросе за пределы HTTP, то же самое касается и очередей, где выбрана нативная PHP сериализация, имеющая множество хейта.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Да и в целом T присматривается к автору PR, ему может показаться, что вы не фанат и язвите и потому вам с Laravel не по пути - закроет PR и попросит Nuno Maduro написать то же самое.🤣

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Но я считаю очень круто, что T настолько сильно вовлечён, продолжая уже много лет каждый день даже в отпуске заходить на GitHub и не прекращая развивать код. Хотя на T ещё куча дел по управлению проектом!

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Команда как ключевой фактор успеха"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

T большое внимание уделяет команде (снова вспоминается фраза про \"too dumb\"). Работой над Laravel занимается команда суперпрофессионалов. Например, T даже не приходится давать правки по дизайну, так как он полностью полагается на опыт и знания своих сотрудников. Но код всё равно оставил за собой! 😉

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Интуиция — это ключ"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

«Всегда окружайте себя людьми, которые умнее и лучше вас»

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ну и T намекает - хотите попасть в мою команду? Развивайтесь, смотрите курсы Laracasts и CutCode 😉.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"История создания Laravel, Forge и Herd"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тут наверное история, которую мы слышим от любого автора open source проекта — то, что сделано было сделано для себя и под свои задачи. Было много боли и рутины на работе и хотелось всё упростить и добиться красивых решений, и вот такие откровения поведал нам и T в этом интервью.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

T сделал Forge (интуиция подсказала), который почти сразу на старте начал приносить ему денег в несколько раз больше, чем на основной работе. Но T подождал ещё 6 месяцев после запуска Forge, так как боялся рисковать, но в итоге ушёл с основной работы и считает, что сделал правильно. Грех не согласится, что выбор правильный, иначе бы у него интервью не брали 😉.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Зачем Laravel масштабироваться?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Из интервью мы узнали, что за год количество сотрудников выросло с 10 до 40 и, соответственно, вопрос - зачем если и так всё хорошо? Оказывается, у T уже давно глобальные цели и ему не хватает ресурсов, чтобы их выполнить. Одна из ближайших целей — это Cloud, который недавно релизнули. Вообще цели это ответ на вопрос, куда движется Laravel?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В целом T считает, что работа над Laravel ещё не закончена и что там есть плохой код, который мешает ему спать🤣. T: \"Откуда в Laravel плохой код скажете вы?!🤣 Да и в целом я думаю ещё надо хотя бы типизацией заняться, но пока ресурсов нет😁\"

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы поняли, что код Laravel идеален и это так, потому что он открыт для всех и это лицо T. А вот в его закрытых коммерческих проектах T сознался, что с кодом уже не так радужно, но главное, что работает и деньги приносит. В общем, там можно и похулиганить.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Из этого вопроса я понял, что T уже устал от работы над Laravel и ему необходимы новые вызовы. Таким вызовом и является проект Cloud, на создание которого он привлёк инвестиции и расширяет команду.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Говоря о будущем, T поставил чёткую цель - сделать порог входа ещё ниже, чтобы web-разработчик ещё не имея особых знаний купил ноут, в пару кликов поднял систему для разработки на любой платформе и начал писать проекты. Ну и также просто их деплоил, конечно же через Cloud (или через onFriday, но про это T почему-то не сказал). В целом T вспоминает, каким простым был PHP, в те старые добрые времена, когда достаточно по FTP залить один файл и всё готово. И насколько сложно деплоить сейчас. Фреймворки всякие с миграциями, гиты, зависимости, очереди.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Собственно цель T сделать PHP снова популярным - чтобы любая обезьянка могла написать сайт и задеплоить его в пару кликов. Кстати говорят, что в Laravel придумали новый вид тестирования, где обезьяны в течение 5 минут должны сделать блог на Laravel, и совсем недавно тест был провален на несколько минут, поэтому вышел Laravel 11 с slim скелетом 😅

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"То, ради чего мы здесь собрались — итоги"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Фокус на массовом разработчике. T не конкурирует с Symfony и с какими-либо Enterprise решениями и не собирается конкурировать. Его путь — работа с массовым потребителем, упор на так называемых junior и middle разработчиков и средние проекты."],"type":"UL"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Философия простоты. Laravel не будет усложняться, так как то, что T не понимает - фреймворку не нужно. Порог вхождения будет дальше уменьшаться. Если требуется без особых знаний ворваться в web-разработку, то Laravel идеален. Песочница или любой не претендующий на хайлоад проект не найдёт лучше решения, чем Laravel. Сам T следует этой философии, делая проекты для экосистемы Laravel (полезные сервисы для упрощения жизни разрабам) - ничего мегасложного."],"type":"UL"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["T держит руку на пульсе и будет продолжать развивать Laravel.T сильно вовлечён в написание кода и занимается код-ревью всех PR в Laravel. Большие планы подтверждают значительные инвестиции, которые удалось привлечь T - https://laravel-news.com/laravel-raises-57-million-series-a. Laravel это работа всей его жизни и его величайшее профессиональное достижение. T намерен продолжать делать его потрясающим","Хейт Laravel оправдан?Сложил мысли T и своё мнение по результатам нескольких лет работы с Laravel и другими PHP фреймворками - хейт Laravel не имеет смысла. У каждого решения есть недостатки, они могут быть явными, или их можно придумать, если есть желание. Это как хейтить Tesla за то, что она не делает тягачи! Действительно, их тачки не способны возить большой груз на дальние расстояния. Но факт в том что самому владельцу Tesla эти недостатки не интересны. Ну если же он купил Tesla для грузоперевозок и его не устраивают возможности авто, то, скорее всего, дурак он, а не Маск."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Laravel отличный инструмент для определенного набора задач. Но он не универсален на 100% и имеет свои недостатки.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ну а T я шлю респект от CutCode! Laravel развивается, хоть и не всем нравится, как это развитие происходит. Slim skeleton только начало, типизацию и уход от нативной сериализации в очередях не ждите, но скоро будет атрибут, который сразу реализует аутентификацию или максимум вынос очередей в пакет 😉

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Всем мир ✌Данил Щуцкий, CutCode

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":1,"favorites":0,"reposts":0,"views":321,"hits":212,"reads":null,"online":0},"dateFavorite":0,"hitsCount":212,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1620989-kuda-dvizhetsya-laravel-obzor-intervyu-s-taylor-otwell","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1583011,"customUri":null,"subsiteId":2317176,"title":"Гайд по деплою web-приложений для новичков. Часть 3. Простая автоматизация","date":1729004757,"dateModified":1729004757,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Привет, коллеги! 👋

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Продолжаем цикл статей, посвященных деплою приложений на сервер.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот ссылки на другие части статьи:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Часть 1. Деплой на shared-хостинг","Часть 2. Настройка окружения и деплой на VPS","Деплой с помощью onFriday"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["bash-скрипт","GitHub actions"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вкратце напомню, что уже изучили, и как мы деплоим вручную:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Есть приватный GitHub-репозиторий с проектом, в который мы пушим изменения с локального репозитория.","Забираем обновленный код из GitHub-репозитория на сервер.","Выполняем команды по обновлению проекта (composer-зависимости, выполняем миграции, пересобираем ассеты, перезапускаем джобы и так далее)."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

То есть кучу команд необходимо запустить и на локальном рабочем месте, и на сервере. И это, конечно же, неудобно. Здесь достаточно много ручной работы, это наводит уныние. Да и можно всего одну команду упустить, и деплой будет неудачным!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте автоматизировать это дело!

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Что у нас есть на старте"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Автоматизировать мы будем деплой проекта на Laravel, репозиторий с демо-версией админки MoonShine - https://github.com/moonshine-software/demo-project.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В этой статье мы автоматизируем деплой проекта, который уже был развернут и настроен. Если вы делаете деплой проекта в первый раз, выполните настройку по этим статьям: shared-хостинг, VPS.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вариант 1. bash-скрипт на сервере"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Самый простой вариант по автоматизации деплоя. Сделаем обычный bash-скрипт, который будем запускать одной командой на сервере, а он будет делать все за нас.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Назовём скрипт easy-deploy.sh.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Приступаем к наполнению скрипта командами. Чтобы интерпретатор понял, что это именно bash-скрипт, мы должны написать вот такую конструкцию в начале файла:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

#!/bin/bash

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Подстелим себе соломку: добавим инструкцию set –e . Если одна из команд завершится с ошибкой, то скрипт прекратит работу. И дальше пишем команды для деплоя (для Composer и миграций желательно использовать флаги --no-interaction и --force соответственно. Они позволяют выполнять команды без необходимости вмешательства пользователя, что важно для автоматизированного процесса деплоя):

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"#!/bin/bash\nset -e\n\n# Переходим в директорию проекта\ncd /var/www/moonshinedemo\n\n# Переводим приложение в режим обслуживания\nphp artisan down\n\n# Обновляем код из GitHub-репозитория\ngit pull\n\n# Устанавливаем зависимости Composer\ncomposer install --no-dev --no-interaction --prefer-dist --optimize-autoloader\n\n# Выполняем миграции базы данных\nphp artisan migrate --force\n\n# Собираем ассеты для production среды\nnpm run build\n\n# Очищаем все кэши\nphp artisan optimize:clear\n\n# Кэшируем\nphp artisan optimize\n\n# Перезапускаем задания в очереди\nphp artisan queue:restart\n\n# Выводим приложение из режима обслуживания\nphp artisan up","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сохраняем. Теперь новую версию приложения попробуем забрать при помощи этого скрипта, и он должен выполнить команды, которые мы прописали.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте проверять. Делаем коммит и обновляем проект на сервере. Вот появился у нас скрипт, давайте запустим его:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sh easy-deploy.sh","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Контролируем процесс.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сейчас процесс деплоя приведен к виду:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Пушим изменения с локального репозитория в GitHub-репозиторий","Подключаемся к серверу (по SSH, как настроили во второй статье) и запускаем скрипт easy-deploy.sh"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Плюсы:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Бесплатно. Всё выполняется без использования платных сервисов","Автоматизация. Каждый деплой позволяет сэкономить пару минут (по сравнению с ручным деплоем)"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Минусы:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Надо подключаться к серверу, чтобы запустить скрипт.","В процессе выполнения деплоя, приложение переводится в режим обслуживания, а это значит что пользователи не смогут получить доступ к сайту.","Отсутствие обратной связи: непонятно как проходит процесс деплоя, необходимо мониторить ход выполнения скрипта на сервере."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По сравнению к деплоем вручную - намного лучше, давайте посмотрим еще один популярный вариант автоматизации.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вариант 2. GitHub Actions"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

GitHub Actions — это инструмент для автоматизации рутинных задач в разработке программного обеспечения. Он позволяет настроить автоматическое тестирование, сборку и деплой приложения. GitHub Actions глубоко интегрирован в экосистему инструментов GitHub.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Итак, у GitHub есть Actions, и можно настроить так, чтобы на событие push в GitHub-репозиторий, GitHub будет запускать необходимые команды на сервере. Что надо сделать:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Создать в локальном репозитории рабочий процесс (workflow) GitHub Actions","Добавить информацию для настройки SSH соединения GitHub-сервер - GitHub secrets","Добавить значения из secrets в файл workflow","Запушить workflow в репозиторий на GitHub","Проверяем работу"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Велосипед выдумывать не будем, работаем по документации GitHub.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Создание рабочего процесса (workflow) GitHub Actions"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создаём свой workflow - создаем папку в каталоге .github/workflows, в ней файл deploy.yml.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Оформим инструкцию!

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Определение события для запуска workflow"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"on:\n push:\n branches:\n - main","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Указываем, что выполняем workflow после события push в ветку main (указываем нужную).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для выполнения команд на удаленном сервере через SSH мы используем appleboy/ssh-action@master (это GitHub Action, созданный пользователем с именем \"appleboy\". Позволяет подключиться к удалённому серверу через SSH и

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

выполнять команды на этом сервере).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Что делает эта Action:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Она предоставляет возможность подключиться к удалённому серверу через SSH.","После подключения можно выполнять команды или скрипты на этом сервере."],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Определение задач (jobs) рабочего процесса"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"jobs:\n\n deploy:\n name: Deploy\n runs-on: ubuntu-latest\n steps:\n - name: SSH Deploy\n uses: appleboy/ssh-action@master\n with:\n host: ${{ secrets.SSH_HOST }}\n username: ${{ secrets.SSH_USERNAME }}\n key: ${{ secrets.SSH_PRIVATE_KEY }}\n script: | \n cd /var/www/moonshinedeploy.ru\n php artisan down\n git pull\n composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader\n php artisan migrate --force\n npm run production\n php artisan optimize:clear\n php artisan optimize\n php artisan queue:restart\n php artisan up","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Файл для работы workflow готов. Обратите внимание на ${{ secrets.SSH_HOST }}, ${{ secrets.SSH_USERNAME }} и ${{ secrets.SSH_PRIVATE_KEY }}. Это переменные для настройки подключению к серверу, которые нам нужно будет создать на GitHub.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настроим secrets GitHub"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хранить информацию по подключению к серверу в файле, который лежит в репозитории, ненадежно.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для хранения конфиденциальной информации (такой как пароли, ключи API и т.д.) придуманы secrets - это переменные, которые вы можете использовать в вашем репозитории GitHub. Secrets предоставляют безопасный способ хранения данных (GitHub обещает, что всё храниться в зашифрованном виде), которые нужны для настройки GitHub Actions workflows.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Secrets находятся в настройках репозитория GitHub - \"Secrets and Variables\", раздел \"Actions\".

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Добавляем информацию для настройки SSH подключения:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"SSH_HOST - IP_адрес_вашего_сервера\nSSH_USERNAME - имя_пользователя","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Переходим к SSH ключам. При настройке SSH-соединения приватный ключ должен храниться на устройстве, с которого вы планируете подключаться к удаленному рабочему месту. А публичный ключ, должен быть скопирован на удаленное рабочее место (к которому мы будем подключаться). Подключаться мы будем с GitHub к серверу. Получается, что мы должны приватный ключ хранить на GitHub, а публичный (.pub) - на сервере.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сгенерируем на сервере пару ключей для нового SSH-соединения \"GitHub-сервер\". При генерации придумайте уникальное название, например, \"github actions\" (чтобы потом проще было найти):

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

ssh-keygen -t rsa -C \"github actions\"

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

- C \"github actions\": добавляет комментарий к ключу. Это помогает идентифицировать ключ, особенно если у вас их несколько.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Указываем имя для файла с ключами:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Enter file in which to save the key (/home/localadmin/.ssh/id_rsa): /home/localadmin/.ssh/gitHubSSH

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Готово!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Копируем значение приватного ключа в секрет SSH_PRIVATE_KEY (начиная от —Begin… до …end ssh private key—).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сам файл приватного ключа можно удалить (на всякий случай, чтобы никто не смог подключиться к нашему серверу).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Итак, secrets настроены, файл с workflow у нас готов, нужно отправить его на GitHub. Создаем новый коммит с изменением, пушим на GitHub.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Смотрим, появился ли наш workflow в разделе Actions.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ждём пока выполнился workflow. Проверяем.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Настраиваем уведомления на почту. Чекбокс стоит, что получаем уведомления о неудачных workflow, если же хотите получать и об успешном выполнении, то снимаем чекбокс.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Плюсы:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Интеграция с GitHub. GitHub Actions полностью интегрирован с GitHub, что упрощает настройку и управление рабочими процессами прямо из вашего репозитория.","Бесплатно (без ограничений) для открытых репозиториев.","Автоматизация. Каждый деплой позволяет сэкономить пару минут (по сравнению с ручным деплоем)"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Минусы:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Ограниченное количество минут GitHub Actions для private-репозиториев на бесплатном тарифе","В процессе выполнения, приложение переводится в режим обслуживания,а значит пользователи не смогут получить доступ к сайту","Отсутствие обратной связи: мониторим ход выполнения workflow или настраиваем уведомления на email","Непростая настройка: новичкам придется попотеть, но как мы убедились это выполнимая задача"],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Выводы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Выбор метода зависит от конкретных потребностей проекта, уровня навыков команды и желаемой степени автоматизации.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В следующей статье покажу, как автоматизировать деплой Laravel-приложений с использованием Laravel Envoy.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ссылки на другие части статьи по деплою:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Часть 1. Деплой на shared-хостинг","Часть 2. Настройка окружения и деплой на VPS","Деплой с помощью onFriday"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

А какой опыт автоматизации деплоя у вас? Возможно, вы используете другие инструменты или у вас есть свои хитрости в настройке процесса? Поделитесь своими мыслями и опытом в комментариях!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данил Щуцкий, автор проекта CutCode.

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":1,"reposts":0,"views":379,"hits":324,"reads":null,"online":0},"dateFavorite":0,"hitsCount":324,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1583011-gaid-po-deployu-web-prilozhenii-dlya-novichkov-chast-3-prostaya-avtomatizaciya","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1569263,"customUri":null,"subsiteId":2317176,"title":"PHP и Laravel дайджест новостей за сентябрь 2024 года","date":1728661524,"dateModified":1728661524,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Всем привет!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это дайджест новостей от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP и Laravel.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Новости PHP"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Первый релиз-кандидат PHP 8.4 доступен для тестирования"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Релиз-менеджеры Calvin Buckley, Saki Takamachi и Eric Mann создали ветку PHP-8.4, теперь разработка будет вестись в ней, а master ветка теперь нацелена на следующую версию PHP. Перед финальным выпуском, который ожидается 21 ноября, нас ожидает еще 3 релиз-кандидата.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вышли PHP 8.1.30, PHP 8.2.24 и PHP 8.3.12"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В этих выпусках исправлены:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Уязвимость инъекции параметров в CGI (CVE-2024-8926).","Обход из-за коллизии переменной окружения директивы cgi.force_redirect (CVE-2024-8927).","Возможность изменения логов от дочерних процессов FPM (CVE-2024-9026).","Ошибки при разборе данных многокомпонентных форм (CVE-2024-8925)."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пожалуйста, обновитесь, как можно скорее.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"PHP-линч #22"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"PHP Russia 2024"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2 и 3 декабря в Москве пройдёт конференция Highload, в рамках которой 16 докладов будут выделены под PHP Russia. Список докладов уже опубликован на сайте конференции.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Laravel привлёк $57 млн в рамках серии A от Accel"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сразу после Laracon US Taylor Otwell опубликовал твит, в котором объявил об инвестициях в размере 57 миллионов долларов от Accel, известной венчурной компании. Поздравляем Тейлора и команду!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кстати, Роман Пронский побывал на Laracon US и пообщался с разными ребятами из PHP-сообщества:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Inside Laracon 2024: PHP's Festival of Innovation and Connection

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Новости ядра PHP"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Большинство новостей ядра PHP подробно освещаются в серии PHP Core Roundup от PHP Foundation, мы лишь быстро по ним пробежимся:

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"📣RFC: Deprecate json_encode() on classes marked as non-serializable"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В PHP, классы, непригодные для сериализации помечаются с помощью флага ZEND_ACC_NOT_SERIALIZABLE, который не позволяет сериализовать экземпляры таких классов с помощью функции serialize().

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Однако этот флаг в настоящее время игнорируется функцией json_encode(), которая является другим способом сериализации, встроенным в PHP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Philip Hofstetter предлагает запретить сериализовать с помощью функции json_encode() большинство экземпляров классов, отмеченных флагом ZEND_ACC_NOT_SERIALIZABLE и выдавать предупреждение об устаревании, начиная с PHP 8.5.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"📣RFC: Change Directory class to behave like an opaque object"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Класс Directory, вероятно, является первым экземпляром того, что мы сейчас называем «непрозрачным объектом». Непрозрачные объекты обычно являются результатом преобразования ресурсов в объекты, что в общем случае подразумевает, что они являются окончательными, не сериализуемыми, не инициализируемыми с помощью ключевого слова new, не могут быть приведены и не реализуют никаких методов. Однако, поскольку этот класс существует со времен PHP 4, ничего из этого формально не реализовано.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Gina Peter Banyard предлагает изменить класс Directory:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Пометить его окончательным.","Выбрасывать ошибку при инициализации с помощью ключевого слова new.","Предотвратить клонирование экземпляров класса Directory.","Запретить сериализацию с помощью doc-комментария @not-serializable к заглушке класса.","Запретить создание динамических свойств для экземпляра класса Directory с помощью doc-комментария @strict-properties к заглушке класса."],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"📣RFC: Warn on conversions from resource to string"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PHP уже давно выполняет различные виды преобразования типов. В то время как большинство сомнительных случаев таких преобразований в настоящее время выбрасывает ошибку TypeError или, по крайней мере, выдает ошибку уровня E_WARNING, при преобразовании ресурса в строку ничего подобного не происходит.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Учитывая, что ресурсы постепенно уходят в сторону непрозрачных объектов и не поддерживают преобразование в строки, Gina Peter Banyard предлагает выдавать ошибку уровня E_WARNING, при преобразовании ресурса в строку.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Laravel дайджест"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Обновления Laravel"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.22 Eloquent inverse relations"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51582

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR, который затрагивает Eloquent и добавляет новый метод, о котором говорил Taylor на последнем Laracon. Он называется Chaperone. Он решает проблему N+1.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте рассмотрим пример:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"@foreach($post->comments as $comment)\n \n @can('promote', $comment)\n promote\n @endcan\n@endforeach","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У нас есть посты, мы также добавляем eager load с комментариями и вынуждены также добавлять eager load с родительским постом у комментариев, чтобы не попасть на исключение при strict mode и в целом, не плодить запросы. Хотя казалось бы, у нас и так есть родительская модель и логично было бы ее засетить ко всем загруженным комментариям. Как раз метод Chaperone это и делает. В pull request он называется inverse, в последующем он был переименован, а также добавлен, видимо, для обратной совместимости. И суть в итоге в том, что мы проходимся по всем загруженным моделькам и делаем set relation родительской записи. Я думаю, многие из вас это делали вручную. Теперь сможем ускорить процесс с помощью нового метода.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.22 Allow enums to be passed to routes"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52561

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Pull request добавляет поддержку указания name в route через enum. Ниже приведен пример:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// before\nRoute::domain(InterfaceDomain::Marketing->value)->name(Routes::Home->value)->get('/contact', ContactController::class);\n\n// after\nRoute::domain(InterfaceDomain::Marketing)->name(Routes::Home)->get('/'contact, ContactController::class);","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

До этого приходилось вызывать value, теперь можно просто указать enum и под капотом все сработает.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.23 Adding minRatio & maxRatio rules on Dimension validation ruleset"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52482

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Первый pull request из этого релиза затрагивает правила валидации и добавлены дополнительные правила, указывающие минимальное и максимальное соотношение сторон.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"function ($attribute, $value, $fail) {\n [$width, $height] = getimagesize($value->getPathname());\n $aspectRatio = $width / $height;\n if ($aspectRatio > 1 / 2 || $aspectRatio < 1 / 3) {\n $fail(\"The image aspect ratio must be between 1:2 and 1:3.\");\n }","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.23 Add BackedEnum support to Gate methods"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52677

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Снова Enum. На этот раз не Route, а Gates, и мы также можем объявлять и далее во многих методах взаимодействовать с Gates через Enum. Как видим в примерах, до этого через строку, теперь то же самое можем делать с Enum, который возвращает строку и соответственно в методах указывать именно enum. Отлично, гораздо удобнее, чтобы не держать эти строки в памяти.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"enum Abilities: string {\n case VIEW_DASHBOARD = 'view-dashboard';\n case EDIT = 'edit';\n case UPDATE = 'update';\n}\n\n\n// Before\nGate::define('view-dashboard', function (User $user) {\n return $user->isAdmin;\n});\n\nGate::authorize('view-dashboard');\nGate::inspect('view-dashboard');\nGate::check('view-dashboard');\nGate::any(['edit', 'update], $post);\nGate::none(['edit', 'update]], $post);\nGate::allows('update', $post)\nGate::denies('update', $post)\n\n// After\nGate::define(Abilities::VIEW_DASHBOARD, function (User $user) {\n return $user->isAdmin;\n});\n\nGate::authorize(Abilities::VIEW_DASHBOARD);\nGate::inspect(Abilities::VIEW_DASHBOARD);\nGate::check(Abilities::VIEW_DASHBOARD);\nGate::any([Abilities::EDIT, Abilities::UPDATE], $post);\nGate::none([Abilities::EDIT, Abilities::UPDATE], $post);\nGate::allows(Abilities::UPDATE, $post)\nGate::denies(Abilities::UPDATE, $post)","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.23 Add BackedEnum support to Authorize middleware"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52679

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Двигаемся дальше по релизу 11.23. Снова Enum. И теперь они также допускаются в объекте Authorize методе using.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"Route::get('/dashboard', [AdminDashboardController::class, 'index'])->middleware(Authorize::using(Abilities::VIEW_DASHBOARD))->name(AdminRoutes::DASHBOARD);","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.23 Add Skip middleware for Queue Jobs"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52645

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Pull request добавляет новый middleware для очередей, который называется Skip. С помощью него можно в методы when и unless указывать условия, при выполнении которых выполнение очереди будет пропускаться. Нам показывают также пример, что раньше автор решал проблему пропуска через замыкание и соответственно сделал middleware, чтобы не дублировать эту логику, хотя в целом и непонятно, почему не добавить условия просто в handle.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"class MyJob implements ShouldQueue\n{\n use Queueable;\n\n public function handle(): void\n {\n // TODO\n }\n\n public function middleware(): array\n {\n return [\n function ($job, $next) {\n if ($someCondition) {\n $next($job);\n }\n },\n ];\n }\n}","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.23 Add Eloquent\\Collection::findOrFail"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52690

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий pull request затрагивает коллекции Eloquent, в которых добавлен метод findOrFail. У новичков часто возникает проблема - после совершения запроса, который возвращает коллекцию с данными, новичок продолжает дергать методы queryBuilder, ожидая, что у нас все еще queryBuilder, а не коллекция, и натыкается на ошибку. Метод findOrFail у нас уже и есть коллекция и он будет по ID искать нужные модельки в этой коллекции. Но я думаю, есть вероятность, что это только добавит путаницы для новичков, но в целом вот такой сахар добавили в Eloquent коллекции.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$users = User::get(); // [User(id: 1), User(id: 2)]\n$users->findOrFail(1); // User\n$user->findOrFail([]); // []\n$user->findOrFail([1, 2]); // [User, User]\n$user->findOrFail(3); // ModelNotFoundException: 'No query results for model [User] 3'\n$user->findOrFail([1, 2, 3]); // ModelNotFoundException: 'No query results for model [User] 3'","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Laracon 2024"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52710

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее у нас огромный pull request от Taylor, на котором я уже сделал обзор на канале, обязательно посмотрите. Это Helper Defer, фасад Concurrency и метод Flexible у кэша. Мы разобрали все подробно, заглянули под капот - обязательно посмотрите ролик на канале.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.23 New when() helper"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52665

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И напоследок по релизу 11.23 это новый Helper When, хотя не совсем Helper, а Blade-директива, благодаря которой можно указать условия и вторым параметром вывести определенные данные, как мы это видим в примерах в этом pull request.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"
\n\n","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.24 add nullOnUpdate() method to ForeignKeyDefinition"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52798

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Pull request затрагивает Schema. До этого у нас был nullOnDelete(), также добавлен и nullOnUpdate(). Кто ожидал - новый сахар добавлен.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$table->foreign('user_id')\n ->references('id')\n ->on('users')\n ->nullOnUpdate();","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.24 Allow BackedEnum to be passed to Route::can()"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/52792

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Снова Enum, снова роуты. Метод can() также принимает Enum начиная с этого релиза.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.25"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также появился релиз 11.25, но он исключительно из фиксов и ревертов, ничего интересного.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Видео-версия дайджеста:

"}},{"type":"video","cover":false,"hidden":false,"anchor":"","data":{"title":"","video":{"type":"video","data":{"thumbnail":{"type":"image","data":{"uuid":"b449e4c3-b2d6-58ce-bd7e-1418010c79cd","width":1280,"height":720,"size":180960,"type":"jpg","color":"1a4658","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQDAwMDAgQDAwMEBAQFBgoGBgUFBgwICQcKDgwPDg4MDQ0PERYTDxAVEQ0NExoTFRcYGRkZDxIbHRsYHRYYGRj/2wBDAQQEBAYFBgsGBgsYEA0QGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBj/wAARCAAKAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABQMG/8QAJxAAAgIAAwYHAAAAAAAAAAAAAQQCAwAFEQYSISIxYRMWM0NRUoH/xAAVAQEBAAAAAAAAAAAAAAAAAAADB//EACERAAECBQUBAAAAAAAAAAAAAAECBAADIjEyBRITIUFC/9oADAMBAAIRAxEAPwBXMdm183UvnUrapa5OsNoSWjDxI1y3gIk9R3GL1KZXXRCHkVbliBzpUa/vDrjE5gyxGijdvtGkOGkj9TgRl54O3AOMepL3D8nvgJrZq4I3hVIAyFhb5itNtDDWVxoXSVKViLqPfsf/2Q=="}},"width":800,"height":450,"time":0,"external_service":{"name":"youtube","id":"IHqtPhyUceg"}}}}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":0,"reposts":0,"views":404,"hits":107,"reads":null,"online":0},"dateFavorite":0,"hitsCount":107,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1569263-php-i-laravel-daidzhest-novostei-za-sentyabr-2024-goda","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1299378,"customUri":null,"subsiteId":2317176,"title":"PHP и Laravel дайджест новостей за июнь 2024 года","date":1720677667,"dateModified":1720677667,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Всем привет!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это PHP Дайджест от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Новости PHP"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вышли PHP 8.1.29, PHP 8.2.20 и PHP 8.3.8

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В этих выпусках исправлены уязвимости:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Инъекция аргументов в PHP-CGI.","Обход фильтра FILTER_VALIDATE_URL в функции filter_var.","Экранирование аргументов для bat- и cmd файлов в Windows окружении для функции proc_open.","Уязвимость к атаке Marvin функции openssl_private_decrypt."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пожалуйста, обновитесь, как можно скорее.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PHP исполнилось 29 лет!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

8 июня 1995 года Rasmus Lerdorf впервые объявил о PHP.Рома Пронский опубликовал ролик, в котором он скомпилировал и запустил первую версию языка. Посмотрите, каким был PHP 29 лет назад.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Running PHP 1.0 in 2024

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

С днем рождения, PHP! 🎉🥳🎂

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Митап на Таганской

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Прошел митап Beer PHP Moscow, на котором выступили с докладами про профилирование и асинхронные PHP-приложения Алексей Сидоркин (Архитектор ГК Т1), Максим Хасанов (Team lead, АльфаСтрахование) и Валентин Удальцов (автор каналов Пых и PHP Point, преподаватель Хардкорного курса PHP).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Lamoda Tech MeetupПрошел еще один митап от команды Lamoda Tech на котором выступили Михаил Мохначёв и Константин Козин, рассказав как почти безболезненно перейти на язык Go PHP-разработчику.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Developer Ecosystem Survey 2024Команда JetBrains запустила восьмое ежегодное исследование экосистем разработчиков, посвященное текущему состоянию индустрии разработки программного обеспечения.Прохождение опроса не займет много времени, а JetBrains, как всегда, поделится результатами исследования.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Projects IDX

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Большинство новостей ядра PHP подробно освещаются в серии PHP Core Roundup от PHP Foundation, мы лишь быстро по ним пробежимся:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

✅ RFC: Add stream open functions to XML{Reader,Writer}

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Niels Dossche предлагает добавить два новых метода для работы с потоком модуля XML:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"XMLReader::fromStream() XMLWriter::toStream()","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣 RFC: Static Constructors

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Erick de Azevedo Lima предлагает добавить новый магический метод __staticConstruct, который будет вызываться автоматически при вызове статического метода.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣 RFC: Static class

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Paul Morris предлагает добавить новый тип класса – статический. Статический класс определяется ключевым словом static, а все методы этого класса автоматически становятся статическими. В настоящее время в PHP можно объявить статическими отдельные методы.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣 RFC: Lazy Objects

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Arnaud Le Blanc и Nicolas Grekas предлагают добавить ленивые объекты в PHP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ленивые объекты не будут инициализироваться до тех пор, пока в этом нет необходимости, например, не будет прочитано или изменено свойство объекта.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ленивые объекты большинство пользователей не будут использовать напрямую, в первую очередь они предназначены для авторов библиотек и фреймворков.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣 RFC: Deprecations for PHP 8.4

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Группа авторов Niels Dossche, Gina Peter Banyard, Máté Kocsis, Tim Düsterhus, Kamil Tekiela и Jorg Sowa запустила обсуждение RFC, чтобы определить какой функционал объявить устаревшим в PHP 8.4 и удалить в PHP 9.0.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Laravel дайджест"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Обновления Laravel"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.10. Allow callback to be passed to updateOrInsert() to pass different $values if the record already exists"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51566

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR затрагивает QueryBuilder, прокачали метод updateOrInsert(). Описание PR начинается с проблемы.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Я думаю, вы знаете, что updateOrInsert() под капотом в себе содержит два запроса. Сперва мы ищем, есть ли такая запись. Соответственно, если есть, то делаем Update, в противном случае Insert. Но если, как в примере у автора:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$values = [\n 'name' => $data['name'],\n 'email' => $data['email'],\n];\n\n$exists = DB::table('users')->where('user_id', $user_id)->exists();\n\nif (! $exists) {\n $values['optional_column'] = $data['foobar'];\n}\n\nDB::table('users')->updateOrInsert(\n ['user_id' => $user_id],\n $values\n);","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Казалось бы, зачем вообще всем этим заниматься? Сделай отдельные два запроса без сахара Laravel, но это не путь Laravel, поэтому прокачали метод updateOrInsert(). Вторым параметром можно теперь передать callback, который в себе будет иметь boolean-значение, есть ли уже запись в таблице, и если есть, мы можем строить дополнительные условия и формировать массив на updateOrInsert():

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"DB::table('users')->updateOrInsert(\n ['user_id' => $user_id],\n function ($exists) use ($data) {\n if ($exists) {\n return [\n 'name' => $data['name'],\n 'email' => $data['email'],\n ];\n }\n\n return [\n 'name' => $data['name'],\n 'email' => $data['email'],\n 'optional_column' => $data['foobar'],\n ];\n }\n\n);","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. Give session ID retrieval the Laravel treatment"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51732

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Первый PR у нас затрагивает сессии, теперь нам не придется писать такой длинный метод getId и появился просто Id:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"use Illuminate\\Support\\Facades\\Session;\n\n- Session::getId();\n\n+ Session::id();","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В изменениях PR видим, что Id это обертка над методом getId.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. Add get, write and forget cache events"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51560

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И соответственно в репозитории, когда сбрасываем кэш или добавляем в кэш, будут дергаться определенные события и теперь появились дополнительные возможности для отслеживания и взаимодействия.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. Add before and after methods to Collection"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51752

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR затрагивает коллекции. Добавлены два новых метода before и after. У нас есть коллекция, как в примере:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$collection = collect([1, 2, 3, 4, 5, 'name' => 'taylor', 'framework' => 'laravel']);\n\n$collection->before(2) // 1\n\n$collection->before('taylor') // 5\n\n$collection->before('laravel') // 'taylor'\n\n$collection->before(fn ($value) => $value > 4) // 4\n\n$collection->before(fn ($value) => ! is_numeric($value)) // 5\n\n$collection->before(1) // null\n\n$collection->before('not found') // null","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Благодаря методу before мы можем указать значение одного из элементов коллекции и получить предыдущее.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Например, через метод before указываем двойку - получаем предыдущее значение - единицу, указываем значение taylor, получаем пятерку. Также поддерживается и callback. After выполняет то же самое действие, только после указанного элемента.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. About command improvement"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51791

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Простой PR, который продолжает улучшать artisan-команду about - добавили переменные timezone и locale из текущего конфига.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. Add Relation::getMorphAlias()"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51809

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий PR добавляет новый метод у класса Relation, чтобы получить alias у Morph-типа модели. Как видим просто указываем модель и получаем alias если он у нас ранее зарегистрирован.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$this->assertDatabaseHas('taskables', [\n 'taskable_type' => Relation::getMorphAlias(Document::class),\n 'taskable_id' => $mitigation->id,\n 'task_id' => $taskB->id\n]);","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. Support third-party relations in model:show command"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51807

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR прокачивает artisan-команду model:show (находит отношения модели). Теперь эта команда будет демонстрировать нам также third-party relations, например из пакетов.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.11. Chop PHP extension when passed to make commands"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51842

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Раньше при вызове команд которые генерируют определенные классы, если указать расширение .php, то у вас будет сформирован класс с двойным расширением. Благодаря этому pull request, если вдруг и прописали расширение, то оно у нас будет тримиться.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

До:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php artisan make:controller UserController.php # Controller [app/Http/Controllers/UserController.php.php] created successfully.","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php artisan make:controller UserController.php # Controller [app/Http/Controllers/UserController.php] created successfully.","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.12. Add multiply to collection"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51870

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR по коллекциям, на этот раз добавили метод multiply. Что он из себя представляет? Например, у нас есть коллекция с определенным набором:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"
\n {{ $user->name }} \n {{ $user->name }} \n {{ $user->name }} \n {{ $user->name }}\n
","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Multiply их будет дублировать и повторно пушить указанное количество раз:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"@foreach($class->students->multiply(4) as $student)\n \n@endforeach","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.12. Add multiply to collection"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51870

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.13. Account for long strings on new Laravel error page"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51880

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.13. Add Support for Extensions in Str::markdown Method"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51907

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий PR затрагивает Helper по работе со строками. Метод Markdown. Третим параметром также принимает набор extension из набора CommonMark:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"public function parseMarkdownFromFile($file_location){\n $markdown_contents = File::get($file_location);\n\n $html = Str::markdown($markdown_contents, [], [\n new AttributesExtension(),\n new TaskListExtension(),\n ]);\n\n return $html;\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Автор PR даже снял минутный ролик о том как это работает. Как это выглядело до и как он прокачал рендер Markdown.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.13. Update config:show command"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51902

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR который прокачивает команду config:show. До этого были проблемы с отображением конфига через \"dot\"-нотацию. Ошибка если конфиг не найден была некорректной - теперь исправили.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.13. Display view creation messages"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51925

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий PR улучшил отображение информации. Раньше при создании компонента у нас отображалось, что компонент по указанному пути успешно создан, но при этом не говорилось о том, что также создана вьюха. Теперь эта проблема решена.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.13. Introduce Str::chopStart and Str::chopEnd"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51910

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Прокачали Helper-класс по работе со строками, добавили несколько новых методов ChopEnd, ChopStart, ReplaceEnd и ReplaceStart. ChopEnd нам дает возможность получить значение строки до указанного выражения и аналогично ChopStar (только указываем после какого значения). Также можно передавать массив с несколькими значениями:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"Str::chopEnd('path/to/file.php', '.php');\n// \"path/to/file\"\n\nStr::chopStart('https://laravel.com', ['https://', 'http://']);\n// laravel.com","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Метод replaceEnd заменяет окончание строки на указанное значение, если эта строка заканчивается на определённую подстроку. Аналогично работает ReplaceStart.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Видео-версия дайджеста:

"}},{"type":"video","cover":false,"hidden":false,"anchor":"","data":{"title":"","video":{"type":"video","data":{"thumbnail":{"type":"image","data":{"uuid":"f0974e76-4aa2-5b83-9df6-8273cd89bbab","width":1280,"height":720,"size":184719,"type":"jpg","color":"1a4557","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQCAwMDAgQDAwMEBAQEBQkGBQUFBQsICAYJDQsNDQ0LDAwOEBQRDg8TDwwMEhgSExUWFxcXDhEZGxkWGhQWFxb/2wBDAQQEBAUFBQoGBgoWDwwPFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhb/wAARCAAKAAoDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAwUGB//EACQQAAEEAQMDBQAAAAAAAAAAAAECAwQFAAYhIhIxYRMUFmKB/8QAFQEBAQAAAAAAAAAAAAAAAAAAAwf/xAAiEQABAgUEAwAAAAAAAAAAAAABAgQAAxIiMQUREyEyQUL/2gAMAwEAAhEDEQA/AGtzpuPbxH1tRXYjs9xoTYKoiUeoltXUAgnuPIw7ESrbYQj4LG4pA517BV+7d8gkSZAp6oiQ6D7dG/WfrmSaks7IaisALCUAJbgADytuavOBMas3JFYXaAPIYGPmKw30UNJHGhdpUpW1Iyo9+4//2Q=="}},"width":800,"height":450,"time":0,"external_service":{"name":"youtube","id":"opk-aA78P7E"}}}}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данил Щуцкий. CutCode

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":4,"favorites":2,"reposts":0,"views":1303,"hits":326,"reads":null,"online":0},"dateFavorite":0,"hitsCount":326,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":true,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1299378-php-i-laravel-daidzhest-novostei-za-iyun-2024-goda","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":27}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1290865,"customUri":null,"subsiteId":2317176,"title":"Гайд по деплою web-приложений для новичков. Часть 2. VPS и настройка окружения","date":1720433818,"dateModified":1720433818,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Привет, коллеги! 👋

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это статья - вторая часть небольшого сериала о деплое web-приложений в поддержку сервиса по деплою приложений onFriday.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот ссылка на первую часть - деплой проекта на Laravel на shared-хостинге с использованием GitHub. Вкратце содержание:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Содержание статьи

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

- создание приватного репозитория на GitHub и его \"связь\" с локальным репозиторием- пуш проекта на GitHub
- клонирование репозитория с GitHub на shared-хостинг
- настройка символических ссылок (необходимо для Laravel приложений)
- настройка Composer
- работа с базой данных- настройка .env, выполнение миграций, компиляция ассетов и рестарт очередей

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если в прошлой статье мы \"ютились\" в отдельном каталоге на shared-хостинге, то в этой части статьи мы будем работать на выделенном сервере (физическом или виртуальном).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Что же такое VPS? VPS (англ. virtual private server) - выделенный виртуальный сервер. Простыми словами это просто удаленный компьютер, который вам временно предоставили. Технически, каждый VPS представляет собой отдельно запущенную виртуальную машину на физическом сервере, но с точки зрения конечного пользователя, такой сервер ничем не отличается по своему функционалу и управлению от выделенного сервера.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На старте у нас будет неподготовленный сервер, и мы с нуля \"накатим\" операционную систему и необходимые инструменты для того, чтобы наше Laravel-приложение работало. Это называется настроить окружение.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В этой статье установим и настроим:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Nginx. Бесплатный и мощный веб-сервер с открытым исходным кодом. Nginx - это приложение, которое может принимать входящие запросы от пользователей (обычно через веб-браузер) и отвечать на них, отправляя запрошенные веб-страницы или другие данные.","Система управления базами данных. Будем использовать MySQL.","Для работы Laravel нам нужен PHP (PHP-FPM).","Далее поставим Composer.","Чтобы корректно работала frontend часть нам понадобится Node/npm.","Ну и Git."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Про Nginx, CGI и PHP-FPM

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Nginx сам по себе не умеет обрабатывать PHP, и если вы направите его на index.php, то в респонсе получите содержимое скрипта. Nginx может обслуживать статические файлы, такие как HTML, CSS и JavaScript, а для обработки PHP-скриптов ему нужна помощь.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

CGI (Common Gateway Interface) - один из первых сценариев обработки php-скриптов сервером. В этом режиме каждый php-запрос выполняется отдельным процессом. Производительность сайта невысокая, так как на обработку скриптов требуется много ресурсов. Сейчас он используется редко и считается устаревшим.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Развитием CGI стал FastCGI. Была учтена медленная скорость CGI и применили циклическую обработку нескольких запросов одним процессом. FastCGI — экономит ресурсы сервера за счет сокращения количества запущенных процессов.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PHP-FPM (FastCGI Process Manager), \"Менеджер процессов FastCGI\". Это уже не протокол или интерфейс, а исполняемая программа, Linux пакет. Это компонент, который реализует протокол FastCGI и соединяет Nginx с нашим Laravel приложением.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По сути, PHP-FPM это балансировщик нагрузки, распределяющий работу между множеством интрефейсов (FastCGI).

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Что у нас есть на старте"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кроме непреодолимого желания, у нас есть выделенный сервер и доступы к нему:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["ip-адрес","логин (в моём случае root)","пароль"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проект для разворачивания как и в предыдущей статье - демо админ-панели для проектов на Laravel - MoonShine.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Git репозитории: локальный и удаленный (приватный) на GitHub уже настроены и связаны (как это делали).

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Установка операционной системы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Смотрим, есть ли операционная система на сервере. В моём случае это Ubuntu 20.04.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У моего провайдера есть возможность выбрать операционную систему через панель управления, возможно у вашего провайдера придется ставить операционную систему самостоятельно или брать дополнительную услугу. Можно приступать! Копируем папку с проектом на сервер (шутка).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Открываем терминал и подключаемся к серверу. Вводим:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"ssh имя_пользователя@ваш_ip_адрес","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вводим пароль. Видим, что приглашение для ввода поменялось - мы авторизованы.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Создаём отдельного пользователя под проект"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хорошей практикой с точки зрения безопасности и управления доступом будет создание отдельного пользователя для каждого проекта что обеспечит изоляцию проектов на одном сервере.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создадим пользователя localadmin и настроим ему директорию:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"useradd -G root -u 1000 -d /home/localadmin -m -k /etc/skel localadmin","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Установим пароль для пользователя localadmin:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"passwd localadmin","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создадим для пользователя директорию .ssh и файл authorized_keys:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"mkdir -p /home/localadmin/.ssh touch /home/localadmin/.ssh/authorized_keys","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Настроим владельца директорий и файлов (папку с проектом создадим позднее, тогда и установим права на неё):

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"chown -R localadmin:localadmin /home/localadmin","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Настроим права доступа:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"chmod 700 /home/localadmin/.ssh chmod 644 /home/localadmin/.ssh/authorized_keys","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы Linux не запрашивал каждый раз пароль при выполнении команд от имени суперпользователя (sudo), выполним команду:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"echo \"localadmin ALL=(ALL) NOPASSWD: ALL\" > /etc/sudoers.d/localadmin","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Входим в систему под созданным пользователем:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"su - localadmin","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настройка подключения по SSH"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Более удобно в дальнейшем подключаться к серверу по SSH:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1. Создадим SSH ключи на локальной машине:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["выполните команду ssh-keygen в терминале","следуйте инструкциям для создания новой пары ключей (публичного и приватного)","по умолчанию ключи будут сохранены в директории:"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Windows - С://Users/username/.ssh/id_rsa (или другой диск, на котором установлена Windows)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Linux - /home/username/.ssh/id_rsa

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

MacOS - /Users/username/.ssh/id_rsa

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2. Добавим публичный ключ (*.pub) на сервер:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["отобразите содержимое публичного ключа на локальном рабочем месте:"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"cat ~/.ssh/id_rsa.pub","lang":""}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["скопируйте содержимое публичного ключа","на сервере откройте файл ~/.ssh/authorized_keys с помощью текстового редактора","вставьте скопированный публичный ключ в конец файла authorized_keys. Убедитесь, что каждый ключ находится на новой строке."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также можно отключить вход на сервер по паролю и разрешить только аутентификацию через SSH. Для этого нужно отредактировать файл конфигурации SSH на сервере:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo nano /etc/ssh/sshd_config\nPasswordAuthentication no ChallengeResponseAuthentication no","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Продолжаем. На старте у нас чистая система и нужно все установить и настроить окружение с нуля.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt update","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Затем можно обновить до последней версии все пакеты в системе:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt full-upgrade","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Nginx"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь можно переходить к установке веб-сервера Nginx:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install nginx","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверим, правильно ли установлен Nginx - перейдём в браузере на ip адрес сервера:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Отлично, появилась стандартная страница Nginx. Чуть позднее разберемся как это работает.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Устанавливаем MySQL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Выполняем команду:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install mysql-server","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Видим, что MySQL 8 версии установлен.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

MySQL поставляется с скриптом mysql_secure_installation, который помогает установить некоторые настройки безопасности по умолчанию: установку пароля root, удаление анонимных пользователей, удаление тестовой базы данных и т.д.:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo mysql_secure_installation","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Запускается диалог, в котором быстро настраивается СУБД:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Validate password component. Можно его не настраивать, но помним что пароль должен быть надёжным - длинный, буквы в разном регистре и т.д.","Remove anonymous users? Удаляем.","Disallow root login remotely? Соглашаемся - запрещаем удаленный вход в систему MySQL от имени пользователя root. Это является хорошей практикой безопасности. Пользователь root в MySQL обладает максимальными привилегиями и может выполнять любые операции с базой данных. Если злоумышленник получит доступ к учетной записи root, он может с нашей базой делать всё что захочет.","Remove test database and access to it? Удаляем тестовую базу данных.","Reload privilege tables now? Перезагружаем чтобы изменения применились"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создадим нового пользователя для базы данных проекта. В целях безопасности всегда нужно создавать отдельного пользователя для базы данных каждого проекта. Заходим в MySQL под root:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo mysql -u root -p","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

и вводим пароль текущего пользователя системы. Вошли, проверяем, что приглашение изменилось на mysql>.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создаём пользователя:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"CREATE USER 'имя_пользователя'@'localhost' IDENTIFIED WITH mysql_native_password BY 'пароль';","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

имя_пользователя и пароль устанавливаем свои!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверим, создан ли пользователь:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"SELECT host, user FROM mysql.user\\G;","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь создаем базу данных для проекта:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"CREATE DATABASE имя_базы;","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И даём пользователю доступ к базе:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"GRANT ALL PRIVILEGES ON имя_базы.* TO 'имя_пользователя'@'localhost';","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"FLUSH PRIVILEGES;","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

База данных подготовлена.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В целях безопасности базы данных можно выполнить еще несколько настроек.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Дополнительные настройки безопасности:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Отключение удалённого подключения. В файле конфигурации MySQL (обычно my.cnf или my.ini) можно указать параметр bind-address и установить его значение на localhost или 127.0.0.1. Это ограничит доступ к MySQL только с локального сервера, и удаленное подключение будет невозможно.","Использование брандмауэра. Настройте брандмауэр на сервере, где установлен MySQL, чтобы блокировать входящие соединения на порт MySQL (обычно 3306) с удаленных IP-адресов."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Переходим к PHP-FPM.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Установка и настройка PHP-FPM"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PHP-FPM ставим чтобы наш веб-сервер мог обрабатывать большое количество запросов одновременно.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Приложение, которое мы будем разворачивать, работает на Laravel 11 - нам понадобиться PHP 8.3 (самая свежая версия PHP на момент публикации статьи, или другую версию, в зависимости от вашего проекта):

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install php8.3-fpm","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверьте установленную версию PHP:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php -v","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В ответ вы должны увидеть информацию о версии PHP

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И статус службы PHP-FPM:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo service php8.3-fpm status","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Служба должна быть активной (запущенной)

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Установка Git"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Переходим к Git:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install git","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверяем:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git –version","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Видим версию git. C git всё достаточно легко, не так ли?

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Установка Composer"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Выполняем команду для загрузки установочного скрипта Composer:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"curl -sS https://getcomposer.org/installer -o composer-setup.php","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверяем что установщик не поврежден и не модифицирован всякими безобразниками:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"HASH=$(curl -sS https://composer.github.io/installer.sig) php -r \"if (hash_file('SHA384', 'composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;\"","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если всё ок и хэш совпал с эталонным, то появится сообщение - Installer verified, значит, установочный файл можно использовать. Устанавливаем Composer:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверим:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"composer --version","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Удаляем файл composer-setup.php :

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php -r \"unlink('composer-setup.php');\"","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Устанавливаем Node.js и npm"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Выполняем:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"curl -sL https://deb.nodesource.com/setup_current.x | sudo -E bash -sudo apt install -y nodejs","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверяем:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"node -v npm -v","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Ставим расширения для PHP"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для каждого проекта ставится свой набор расширений для PHP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот самые популярные:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-mysql — позволяет PHP взаимодействовать с базами данных MySQL. Предоставляет функции и классы для подключения к MySQL, выполнения запросов и обработки результатов.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-curl — обеспечивает интеграцию с библиотекой cURL. cURL (Client URL) — это инструмент для работы с URL-адресами, который позволяет выполнять HTTP-запросы, обращаться к веб-серверам, загружать файлы и многое другое. Модуль php-curl позволяет PHP-скриптам использовать функциональность cURL для взаимодействия с внешними ресурсами, такими как API, веб-сервисы и другие серверы.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-dom — позволяет взаимодействовать с XML-документами через DOM API. Оно предоставляет функции и классы для работы с иерархической структурой дерева узлов.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-zip — позволяет читать/записывать сжатые ZIP-архивы и файлы внутри них.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-intl— предоставляет поддержку интернационализации (i18n) и локализации (l10n). Это расширение включает в себя библиотеку ICU (International Components for Unicode), которая обеспечивает мощные инструменты для работы с различными языками, региональными настройками и кодировками.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-mbstring — предоставляет поддержку многобайтовых строк (multibyte strings). Многобайтовые строки используются для представления символов, которые не могут быть закодированы в одном байте, таких как символы азбуки Морзе, китайские иероглифы, японские каны и многие другие.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-soap — обеспечивает поддержку для работы с SOAP (Simple Object Access Protocol). SOAP — это протокол для обмена структурированными информационными сообщениями в распределенной среде, использующий XML в качестве формата сообщений.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-cli — предоставляет возможность запускать PHP скрипты из командной строки. В контексте Laravel, может использоваться для выполнения команд Laravel Artisan.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-common — является одним из основных пакетов расширений PHP, который включает в себя общие и основные функции и библиотеки, необходимые для работы с PHP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-fpm — альтернативный способ запуска PHP скриптов на веб-сервере, является отдельным процессом, который управляет выполнением PHP скриптов и обеспечивает более эффективное управление процессами PHP на сервере.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-gd — (Graphics Draw) для работы с изображениями. Позволяет создавать, редактировать и обрабатывать изображения в различных форматах, таких как JPEG, PNG, GIF и другие.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-opcache — для улучшения производительности PHP приложений путем кэширования скомпилированного PHP кода в памяти сервера. Это позволяет уменьшить время выполнения PHP скриптов, так как PHP код не нужно компилировать заново при каждом запросе.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-readline — делает работу с командной строкой более удобной и эффективной (автодополнение и история команд, перемещение курсора и т.д.)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

php-xml — для работы с XML (Extensible Markup Language) - создание XML документов, их парсинг, преобразование в другие форматы данных и т.д.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Я установил следующие:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install php-mysql php-curl php-dom php-zip php-intl php-mbstring php-soap php-opcache","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В вашем случае список модулей может отличаться. Если вы не уверены, какие модули уже установлены, выполнить команду php -m .

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Расширения для PHP будут установлены для той версии, которая используется на вашем сервере

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Каталог проекта и настройка прав доступа"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте создадим директорию для проекта который будем деплоить:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo mkdir /var/www/moonshinedemo","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для того чтобы веб-сервер Nginx мог корректно работать с файлами и директориями нашего проекта на Laravel, он должен иметь правильные права доступа, чтобы обеспечить как безопасность, так и работоспособность приложения. При установке Nginx создаётся пользователь www-data или nginx (проверить можно в файле с конфигурацией /etc/nginx/nginx.conf, директива user). В моём случае пользователь Nginx - www-data.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Общий подход настройки прав доступа для директории проекта заключается в том, чтобы установить владельца (пользователя и группу localadmin) для полного контроля и дать права Nginx на чтение.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Делаем localadminвладельцем директории проекта:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo chown -R localadmin:localadmin /var/www/moonshinedemo","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь настроим для Nginx. Владельца оставляем localadmin, а группу установим www-data:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo chown -R localadmin:www-data /var/www/moonshinedemo","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Установим права на директорию проекта:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["для директорий: 755 (чтение и выполнение для всех, запись для владельца)","для файлов: 644 (чтение и запись для владельца, чтение для остальных)"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo find /var/www/moonshinedemo -type d -exec chmod 755 {} + sudo find /var/www/moonshinedemo -type f -exec chmod 644 {} +","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Наконец, отдельно для директорий storage и bootstrap/cache устанавливаем права доступа:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["для директорий: 775 (чтение и выполнение для всех, запись для членов группы владельца)","для файлов: 664 (чтение и запись для членов группы владельца, чтение для остальных)"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo find /var/www/moonshinedemo/storage -type d -exec chmod 775 {} + \nsudo find /var/www/moonshinedemo/bootstrap/cache -type d -exec chmod 775 {} + sudo find /var/www/moonshinedemo/storage -type f -exec chmod 664 {} + \nsudo find /var/www/moonshinedemo/bootstrap/cache -type f -exec chmod 664 {} +","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь у Nginx есть право записывать данные в эти директории, что необходимо для кэширования и логирования.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настройка Nginx"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если вы не знаете как работает Nginx, то предлагаю краткий экскурс.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Nginx сейчас выводит свою дефолтную страницу, давайте научимся влиять на поведение Nginx!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Nginx у нас \"живет\" в каталоге ../etc/nginx :

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo nano /etc/nginx/sites-available/default","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Его конфиг по умолчанию находится в /sites-available. Объясню отдельные строки конфига:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"listen 80 default_server # web-сервер работает на 80 порту сервера \nroot /var/www/html # директория проекта \nindex index.html index.htm index.nginx-debian.html # варианты названий файлов-стартовых точек из директории проекта","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создадим в каталоге проекта /var/www/moonshinedemo файл для тестирования - index.php:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"touch /var/www/moonshinedemo/index.php","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь откроем этот файл с помощью редактора nano:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"nano /var/www/moonshinedemo/index.php","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте что-нибудь оригинальное тут изобразим. Пусть будет

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Жмем ctrl+s (сохраняет файл) и ctrl+x (закрыть редактор nano).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь надо указать Nginx чтобы он искал проект в папке moonshinedemo. Внесем изменения в конфиг:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo nano /etc/nginx/sites-available/default","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Редактируем следующие строки:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"root /var/www/moonshinedemo index index.php","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

\"Учим\" Nginx как работать с php файлами. Раскомментируем строки:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"location ~ \\.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; }","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы изменения в конфигурацию применились, надо Nginx перезапустить:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo systemctl reload nginx","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И проверяем работу, вводим ip-адрес:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Всё работает, файл index.php в /var/www/moonshinedemo можно удалить.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"rm /var/www/moonshinedemo/index.php","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Клонируем проект из GitHub"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Переходим в каталог /var/www/ и клонируем проект из GitHub репозитория в каталог moonshinedemo:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"cd /var/www/moonshinedemogit clone git@github.com:CutCodes/CutCodeDeploy.git .","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверим:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"ls -la","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Всё ок, мы склонировали проект на сервер.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Остальные телодвижения"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обновляем зависимости:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"composer install","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создаём .env из шаблона:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"cp .env.example .env","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Генерируем APP_KEY, вносим настройки для работы с базой данных, переводим в прод, указываем APP_URL , (смотрим как это делать в первой статье). После запускаем миграции:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php artisan migrate","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пересобираем ассеты:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"npm install npm run build","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php artisan storage:link","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После того, как символическая ссылка создана, она не требует обновления при каждом деплое. Заново создать символическую ссылку нужно только если произошли изменения в структуре файлов или директорий, связанных с хранением файлов (например, изменение пути к директории storage).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В настройках Nginx указываем параметры из документации Laravel (Тейлор всё протестировал). Не забудьте поменять путь к проекту (root):

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"server {\n  listen 80;\n  listen [::]:80;\n  server_name example.com;\n  root /var/www/moonshinedemo/public;\n\n  add_header X-Frame-Options \"SAMEORIGIN\";\n  add_header X-Content-Type-Options \"nosniff\";\n\n  index index.php;\n\n  charset utf-8; \n\n   location / {\n      try_files $uri $uri/ /index.php?$query_string;\n   }\n\n   location = /favicon.ico { access_log off; log_not_found off; }\n   location = /robots.txt  { access_log off; log_not_found off; }\n\n  error_page 404 /index.php;\n\n   location ~ \\.php$ {\n      fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;\n      fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n      include fastcgi_params;\n      fastcgi_hide_header X-Powered-By;\n   }\n\n   location ~ /\\.(?!well-known).* {\n      deny all;\n   }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Инструкция location / с директивой try_files $uri $uri/ /index.php?query_string; в конфигурации Nginx для приложения на Laravel используется для обработки маршрутов и перенаправления всех запросов на index.php, где происходит дальнейшая обработка маршрутов приложения Laravel.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Перезагружаем Nginx:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo systemctl reload nginx","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Готово. Можно проверять!

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Дополнительные работы по Supervisor и Cron"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для продвинутых пользователей добавил информацию по установке и настройке Supervisor и Cron на ваш сервер. Полезные инструменты!

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Supervisor"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Зачем нам нужен Supervisor?

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Очереди - неотъемлемая часть любого Laravel приложения. Не будем затрагивать тему как они работают, все это есть в документации. А Supervisor я добавил в статью при деплой потому, что если мы без него будем использовать queue:work (обработчик очередей, который обрабатывает задачи, помещенные в очередь), то при перезапуске или сбое приложения обработчик перестанет работать! Тут нам и поможет Supervisor, это менеджер процессов, который работает в фоновом режиме (демон) и управляет другими процессами, такими как queue:work. Supervisor который контролирует рабочие процессы и их количество, а также перезапустит процессы в случае сбоя.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте установим Supervisor:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install supervisor","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для настройки Supervisor необходимо создать конфигурационный файл для каждого процесса, которым вы хотите управлять. Перейдем в директорию Supervisor (обычно это /etc/supervisor/conf.d/) и создадим конфигурационный файл для обработчика очередей Laravel - laravel-worker.conf :

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"[program:laravel-worker]\n\n   process_name=%(program_name)s_%(process_num)02d\n\n   command=php /путь-к-вашему-проекту/artisan queue:work --sleep=3 --tries=3\n\n   autostart=true\n\n   autorestart=true\n\n   user=www-data\n\n   numprocs=8\n\n   redirect_stderr=true\n\n   stdout_logfile=/путь/к/лог-файлу/worker.log","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обновите Supervisor, чтобы он прочитал новый конфигурационный файл:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo supervisorctl reread \nsudo supervisorctl update","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Запустите обработчика очередей с помощью Supervisor:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo supervisorctl start laravel-worker","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь Supervisor будет управлять процессом обработки очередей Laravel и обеспечивать его непрерывную работу.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Сron"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Cron - это стандартный инструмент в операционных системах Unix, который позволяет запускать задачи по расписанию. Эти задачи могут выполняться автоматически в определенное время или с определенной периодичностью.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы же с помощью Cron настроим чтобы каждую минуту выполнялась консольная команда Laravel schedule:run, которая под капотом будет искать задачи, которые нужно выполнить прямо сейчас. Создаётся команда так:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php artisan make:command ResetCommand","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

При этом создается файл команды - app/Console/Commands/ResetCommand.php

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте разберем пример. На демо-проекте админки MoonShine - работает команда, которая ресетит базу данных и чистит пользовательские файлы чтобы они не копились. Демо проект размещен чтобы любой желающий мог попробовать MoonShine в деле и в том числе добавить свои пользовательские данные (создать статьи, добавить изображения и т.д.). Чтобы база данных не разрасталась, сделана команда по очистке:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"call('migrate:fresh', [\n            '--force' => true,\n            '--seed' => true\n        ]);\n        File::cleanDirectory(storage_path('app/public'));   //очищаем временные файлы\n        return self::SUCCESS;    //возвращаем что команда выполнена\n    }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы команда выполнялась её нужно добавить в файле routes/console.php с указанием периодичности выполнения. Приготовлены такие функции, как ->daily(), ->hourly(), ->everyFifteenMinutes() и т. д.:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"everyFifteenMinutes();","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Переходим на сервер. На Ubuntu 20.04 Сron обычно уже предустановлен по умолчанию. Вы можете проверить, установлен ли cron, с помощью следующих команд:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo dpkg -l | grep cron","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если Cron не установлен выполняем команду:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo apt install cron","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После установки Cron будет автоматически запущен. Проверяем его статус с помощью команды:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo systemctl status cron","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Настроим автозапуск Crone при загрузке системы:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"sudo systemctl enable cron","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Добавляем планировщика задач Laravel в Cron. Выполните команду crontab -e, которая откроет файл редактирования текущих задач:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"* * * * * cd /путь_к_вашему_проекту & php artisan schedule:run >> /dev/null 2>&1","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Готово. Теперь Cron будет следить что планировщик задач Laravel (scheduler) запускается каждую минуту. А планировщик, в свою очередь, запускает команду для очистки базы данных каждые 15 минут.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Выводы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Деплой на VPS сервер выполнен! Этот процесс не сложнее деплоя на shared-хостинг, если делать всё пошагово, то проблем обычно не возникает. В среднем процесс создания окружения занимает минут 40-50. Сам в работе использую только VPS серверы, разной конфигурации, в зависимости от решаемых задач.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"#Обновляем все пакеты в системе\nsudo apt update\nsudo apt full-upgrade\n\n#NginxОбновите Supervisor, чтобы он прочитал новый конфигурационный файл:\n\nЗапустите обработчика очередей с помощью Supervisor:\n\nТеперь Supervisor будет управлять процессом обработки очередей Laravel и обеспечивать его непрерывную работу. \n\nСron\n\nCron - это стандартный инструмент в операционных системах Unix, который позволяет запускать задачи по расписанию. Эти задачи могут выполняться автоматически в определенное время или с определенной периодичностью.\n\nВ Laravel есть мощный функционал для планирования задач по расписанию, который называется scheduler. Также не буду перегружать статью про то как он работает, всё хорошо описано в документации. \n\nМы же с помощью Cron настроим чтобы каждую минуту выполнялась консольная команда Laravel schedule:run, которая под капотом будет искать задачи, которые нужно выполнить прямо сейчас. Создаётся команда так:\n\nПри этом создается файл команды - app/Console/Commands/ResetCommand.php\n\nДавайте разберем пример. На демо-проекте админки MoonShine - работает команда, которая ресетит базу данных и чистит пользовательские файлы чтобы они не копились. Демо проект размещен чтобы любой желающий мог попробовать MoonShine в деле и в том числе добавить свои пользовательские данные (создать статьи, добавить изображения и т.д.). Чтобы база данных не разрасталась, сделана команда по очистке:\n\nЧтобы команда выполнялась её нужно добавить в файле routes/console.php с указанием периодичности выполнения. Приготовлены такие функции, как ->daily(), ->hourly(), ->everyFifteenMinutes() и т. д.:\n\nПереходим на сервер. На Ubuntu 20.04 Сron обычно уже предустановлен по умолчанию. Вы можете проверить, установлен ли cron, с помощью следующих команд:\n\nЕсли Cron не установлен выполняем команду:\n\nПосле установки Cron будет автоматически запущен. Проверяем его статус с помощью команды:\n\nНастроим автозапуск Crone при загрузке системы:\n\nДобавляем планировщика задач Laravel в Cron. Выполните команду crontab -e, которая откроет файл редактирования текущих задач:\n\nГотово. Теперь Cron будет следить что планировщик задач Laravel (scheduler) запускается каждую минуту. А планировщик, в свою очередь, запускает команду для очистки базы данных каждые 15 минут.\n\nВыводы\n\nДеплой на VPS сервер выполнен! Этот процесс не сложнее деплоя на shared-хостинг, если делать всё пошагово, то проблем обычно не возникает. В среднем процесс создания окружения занимает минут 40-50. Сам в работе использую только VPS серверы, разной конфигурации, в зависимости от решаемых задач.\n\nДля вашего комфорта подготовил шпаргалку с командами по настройке окружения, чтобы ничего не забыть, и не листать статью в поисках нужной строчки.\n\nЧто дальше?\n\nВарианты деплоя на самые популярные виды хостинга мы рассмотрели. В следующей статье будем разбираться, как можно (и нужно) автоматизировать деплой. Подписывайтесь на мой блог, чтобы не пропустить! К первой части статьи было достаточно много комментариев, в том числе по дополнению статьи, так что жду активную обратную связь и в этот раз. \n\nДанил Щуцкий, автор проекта CutCode.\nsudo apt install nginx\n\n#MySQL\nsudo apt install mysql-server\nsudo mysql_secure_installation\nsudo mysql -u root –p\nCREATE USER 'имя_пользователя'@'localhost' IDENTIFIED WITH mysql_native_password BY 'пароль';\nSELECT host, user FROM mysql.user\\G;\nCREATE DATABASE имя_базы;\nGRANT ALL\nPRIVILEGES ON имя_базы.* TO 'имя_пользователя'@'localhost';\nFLUSH PRIVILEGES;\n \n#PHP FPM\nsudo add-apt-repository ppa:ondrej/php\nsudo apt install php8.3-fpm\n\n#Git\nsudo apt install git\n\n#Composer \ncurl -sS https://getcomposer.org/installer -o composer-setup.php\nphp -r \"if (hash_file('SHA384', 'composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;\"\nsudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer\nphp -r \"unlink('composer-setup.php');\"\n\n#Node.js и npm\ncurl -sL https://deb.nodesource.com/setup_current.x | sudo -E bash -\nsudo apt install -y nodejs\n\n#Расширения для PHP\nsudo apt install php-mysql php-curl php-dom php-zip php-intl php-mbstring php-soap php-opcache\n\n-----------\n#Настройка Nginx\nРедактируем настройки\nsudo nano /etc/nginx/sites-available/default\n\n#Настройки Nginx для Laravel\nserver {\n listen 80;\n listen [::]:80;\n server_name example.com;\n root /var/www/директория/public;\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n error_page 404 /index.php;\n \n location ~ \\.php$ {\n fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include fastcgi_params;\n fastcgi_hide_header X-Powered-By;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n}\n\n#Перезагрузка Nginx\n\nsudo systemctl reload nginx\n\n-----------\n#Создаём директорию для проекта\nsudo mkdir /var/www/директория\n\n#Устанавливаем на директорию текущему пользователю и Nginx\nsudo chown -R имя_пользователя:имя_пользователя /var/www/moonshinedemo\nsudo chown -R имя_пользователя:www-data /var/www/moonshinedemo\nsudo find /var/www/директория -type d -exec chmod 755 {} +\nsudo find /var/www/директория -type f -exec chmod 644 {} +\nsudo find /var/www/директория/storage -type d -exec chmod 775 {} + \nsudo find /var/www/директория/bootstrap/cache -type d -exec chmod 775 {} +\nsudo find /var/www/директория/storage -type f -exec chmod 664 {} + \nsudo find /var/www/директория/bootstrap/cache -type f -exec chmod 664 {} +\n\n-----------\n#Деплой приложения\n#Копируем проект:\ngit clone git@github.com:ссылка_на_проект.git .\n\n#Обновляем зависимости:\ncomposer install\n\n#Создаём .env из шаблона:\ncp .env.example .env\n\n#Генерируем APP_KEY:\nphp artisan key:generate\n\n#Вносим информацию в файл .env (база данных, прод, APP_URL)\n\n#Запускаем миграции:\nphp artisan migrate\n\n#Пересобираем ассеты:\nnpm install\nnpm run build","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Что дальше?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Варианты деплоя на самые популярные виды хостинга мы рассмотрели. В следующей статье будем разбираться, как можно (и нужно) автоматизировать деплой. Подписывайтесь на мой блог, чтобы не пропустить! К первой части статьи было достаточно много комментариев, в том числе по дополнению статьи, так что жду активную обратную связь и в этот раз.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данил Щуцкий, автор проекта CutCode.

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":4,"favorites":2,"reposts":0,"views":844,"hits":703,"reads":null,"online":0},"dateFavorite":0,"hitsCount":703,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1290865-gaid-po-deployu-web-prilozhenii-dlya-novichkov-chast-2-vps-i-nastroika-okruzheniya","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1290754,"customUri":null,"subsiteId":2317176,"title":"Гайд по деплою web-приложений для новичков. Часть 1. Shared-хостинг","date":1720432845,"dateModified":1720432845,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Привет, коллеги! 👋

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Этот гайд я создал в поддержку сервиса по деплою приложений onFriday.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В web-разработке процесс деплоя играет важную роль. Это ответственный момент (даже торжественный), когда все усилия, потраченные на написание и тестирование кода, воплощаются в “живое” приложение, доступное пользователям. Ведь для этого приложение и делается, чтобы им кто-то пользовался. Каждый разработчик, независимо от уровня и специализации, регулярно сталкивается с задачей деплоя. Это статья в основном рассчитана на новичков, которые учатся разворачивать приложения на сервере и хотят узнать различные варианты, сравнить их и выбрать подходящий.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Деплой - это не просто загрузка кода на сервер (хотя лет 20 назад деплоили приложения по FTP). Прогресс не стоит на месте, приложения стали сложнее и сейчас деплой - это сложный и многоступенчатый процесс, который обычно включает в себя подготовку окружения, установку зависимостей, миграцию базы данных, минимизацию и компиляцию ассетов, проверку работоспособности и, наконец, переключение трафика на новую версию приложения.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ссылки на другие части гайда:
Часть 2. VPS и настройка окружения

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также в рамках этой статьи будем использовать GitHub. Я в работе использую именно его, наверное потому что Laravel использует GitHub (шутка). Всем начинающим разработчикам настоятельно рекомендую использовать git и привыкать к современным инструментам и принципам работы в команде, где без системы контроля версий никак не обойтись.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Вариант деплоя с архивом"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Этот вариант уже в архиве истории. Его добавил для того чтобы немного поднять настроение. Брали папочку, подключались по ftp на сервер (я использовал программку FileZilla), загружали архив на сервер и там его распаковывали. Для сайтов-визиток работало неплохо. Всё что сложнее уже могут возникнуть проблемы.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"\"Классический\" деплой на shared-хостинге с использованием GitHub"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Считаю что этот \"классический\" способ должен знать каждый начинающий разработчик, чтобы понимать, что происходит при развертывании приложения на сервере. Используется при этом недорогой shared-хостинг (рублей 200-300/месяц на Timeweb, Beget, Рег.ру и т.д.) без необходимости администрирования. Подойдет для начинающих разработчиков. Дешево и сердито. В этой статье я буду показывать как деплоить на хостинге от beget (можно любой другой).

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Требования к хостингу"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["поддерживает версию PHP, которая соответствует вашему проекту (в моём случае 8.3 - настоятельно рекомендую своевременно обновлять свои проекты)","есть система управления базами данных MySQL","есть возможность подключения по SSH","установлена система контроля версий Git"],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Что еще понадобиться"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["домен связан с хостингом (DNS-серверы хостинга внесены в настройки домена), или есть тестовый домен для проверки работоспособности проекта","на рабочем месте установлена система контроля версий Git","есть аккаунт на GitHub"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проще говоря - на хостинге уже есть папка с сайтом и в браузере можно вбить домен и сайт будет открываться.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кроме этого понадобятся знания основ php, git и devops. Ну раз вы занялись деплоем, то наверняка вы разбираетесь в основах.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Начинаем.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настройка git репозиториев"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Нажимаем кнопку \"Create repository\". Репозиторий создан и GitHub предлагает нам пошаговую инструкцию - Quick setup. Давайте по ней и работать, чтобы ничего не забыть.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Приступаем.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Инициализируем новый локальный Git репозиторий на компьютере при помощи команды git init .

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"cd \"путь_к_папке_с_вашим_проектом\" git init","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Эта команда создала папку .git внутри папки с вашим проектом.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хорошее начало. Осталось совсем немного.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы Git понимал какие файлы в проекте изменились, нужно добавить их в индекс.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git add -A","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Эта команда добавляет содержимое текущего каталога в индекс. И теперь git будет отслеживать изменения в репозитории и какие из файлов мы добавим в следующий коммит.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пора сделать первый commit проекта (с комментарием \"исходный проект\") в созданный репозиторий:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git commit -m \"исходный проект\"","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

и настраиваем ветку главной:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git branch -M master","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Представьте, что у вас есть версия проекта (игра, прогресс в которой вы хотите сохранить) на вашем компьютере (это ваша приставка), и вы хотите загрузить его на GitHub, чтобы другие могли им воспользоваться или чтобы вы могли работать над ним с другого компьютера.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Но прежде чем вы сможете загрузить свой проект туда, вы должны \"связать\" ваш локальный репозиторий на компьютере с удаленным репозиторием на GitHub. Так как у нас репозиторий приватный, то нужно настроить безопасное соединение между компьютером и GitHub через SSH.

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Создание SSH ключей на локальном компьютере:Откройте терминал и выполните команду для создания нового SSH ключа:ssh-keygen","Добавьте SSH ключ в агент ssh:eval \"$(ssh-agent -s)\" ssh-add ~/.ssh/id_rsa","Копирование публичного ключа:cat ~/.ssh/id_rsa.pub","Добавление публичного ключа на GitHub:Перейдите в \"Settings\" > \"SSH and GPG keys\".Нажмите \"New SSH key\" и вставьте ваш публичный ключ, а затем нажмите \"Add SSH key\"."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пора связать репозитории! Укажем удаленный репозиторий на GitHub с помощью команды:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git remote add origin https://github.com/\"имя_пользователя\"/\"имя_репозитория\".git","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Замените имя_пользователя и имя_репозитория на ваше имя пользователя на GitHub и имя вашего репозитория соответственно.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git remote add origin https://github.com/CutCodes/CutCodeDeploy.git","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь, когда ваш локальный репозиторий связан с удаленным репозиторием на GitHub, вы можете запушить (или отправить) свой проект на GitHub. Это можно сделать с помощью команды git push origin master:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы сделать это, вы можете установить ветку master вашего репозитория как ветку по умолчанию для команды git push. Это можно сделать с помощью следующей команды:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git push -u origin master","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Отлично. Проект на GitHub! И теперь, когда вы вводите git push, Git автоматически пушит ваши изменения в ветку master вашего удаленного репозитория на GitHub. Это делает процесс обновления вашего проекта на GitHub намного быстрее и проще!

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Клонируем проект на хостинг из GitHub"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы это сделать, мы должны установить безопасное соединение между нашим сервером и репозиторием на GitHub.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Для начала, переходим на нашем сервере в консоль. Затем мы создаем новую пару ключей SSH - это приватный и публичный ключи. Эти ключи нужны для установки безопасного соединения.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

SSH-ключи создаются командой ssh-keygen. Ключи обычно хранятся на сервере в каталоге ~/home/\"имя_пользователя\"/.ssh/\"имя_файла\".

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После того как ключи сгенерированы, в консоли сразу же указывается путь до них:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Приватный ключ: ~/home/\"имя_пользователя\"/.ssh/\"имя_файла\"","Публичный ключ: ~/home/\"имя_пользователя\"/.ssh/\"имя_файла\".pub"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь нам нужно добавить публичный ключ на GitHub. Копируем путь к публичному ключу, и при помощью команды cat смотрим его содержимое:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"cat ~.ssh/\"имя_файла\".pub","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Копируем содержимое публичного ключа и переходим в настройки нашего репозитория на GitHub (это раздел \"Settings\"). Затем мы выбираем раздел \"Deploy Keys\".

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В этом разделе мы добавляем новый ключ деплоя. Мы даем ему название (например, \"CutCodeDeploy.beget.tech\") и вставляем наш скопированный ключ в соответствующее поле.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После этого мы подтверждаем добавление ключа, нажав на кнопку \"Add key\".

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Важное примечание.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На GitHub еcть опция в виде чекбокса, которая позволяет разрешить запись в репозиторий. Если этот чекбокс не выбран, то по настроенному SSH-соединению можно будет только считывать содержимое репозитория, но не вносить в него изменения.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это важно помнить при настройке SSH-соединения с GitHub.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На GitHub, в нашем репозитории, мы выбираем раздел \"Code\", затем переходим на вкладку \"ssh\" и копируем адрес репозитория.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Заходим на сервер, переходим в директорию проекта и в консоли выполняем необходимую команду.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git clone git@\"скопированный_адрес_репозитория\"","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Команда git clone выполняет несколько действий: она инициализирует репозиторий Git в текущей директории, добавляет удаленный репозиторий (известный как \"origin\") с GitHub, и затем клонирует содержимое этого репозитория в текущую директорию.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настроим git pull"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

При клонировании репозитория с помощью Git, ссылка на исходный репозиторий автоматически устанавливается как origin. Это означает, что Git знает, откуда получать данные при выполнении git pull.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Когда вы хотите забрать обновления с GitHub, вам просто нужно написать git pull, и Git автоматически заберет обновления из ветки master репозитория origin, как почтальон, который знает, где ваш почтовый ящик.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настройка символических ссылок"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Точка входа в веб-приложении - это файл, который обрабатывает все запросы к вашему веб-сайту и перенаправляет их в ваше приложение. Обычно на shared-хостингах это файл index.php или index.html, который находится в корневом каталоге веб-приложения.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У нас приложение на Laravel. В Laravel точка входа обычно находится в каталоге public. Это сделано для обеспечения безопасности, так как только файлы в каталоге public доступны для прямого доступа, а все остальное - под замком. Круто придумано.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Итак, точка входа в приложение Laravel - это файл public/index.php, и нам нужно, чтобы все запросы по умолчанию направлялись на public/index.php. Простое решение этой задачи - создать символическую ссылку. Это как создать ярлык на рабочем столе, который ведет к нужной программе.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если в корне проекта уже есть файл index.php или index.html, то сначала удалим его. А теперь давайте создадим символическую ссылку. Перейдем в корневой каталог вашего проекта Laravel и выполним нужную команду

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"ln -s public public_html","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это означает, что теперь у вас есть новый путь public_html, который ведет прямо в ваш каталог public.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Обновляем зависимости"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Composer - это мощный инструмент для управления зависимостями в PHP. Он позволяет вам объявлять библиотеки, которые ваш проект использует, и он управляет (устанавливает/обновляет) их за вас. Это как ваш личный помощник, который следит за тем, чтобы все ваши библиотеки были в порядке.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Composer будет устанавливать все зависимости, указанные в файле composer.json вашего проекта. Это как список покупок для вашего приложения, по которому Composer будет следовать.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На некоторых хостингах Composer может быть не установлен по умолчанию. Но обычно у хостинг-провайдеров есть подробные инструкции о том, как установить Composer на их серверах.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Что делать если на хостинге нет Composer?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если Composer не установлен на сервере, вы всегда можете установить его локально в ваш проект и после этого обновить проект на сервере. Если Composer есть, то пропускаем эту главу.

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Сначала мы загружаем установочный файл Composer с помощью команды:"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php -r \"copy('https://getcomposer.org/installer', 'composer-setup.php');\"","lang":""}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Затем мы устанавливаем Composer с помощью команды:"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php composer-setup.php","lang":""}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["После завершения установки мы можем удалить установочный файл, он нам больше не понадобится:"],"type":"UL"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php -r \"unlink('composer-setup.php');\" mv composer.phar /usr/bin/composer","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь в вашем проекте есть файл composer.phar, и его можно использовать для управления зависимостями внутри вашего проекта.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Чтобы Composer появился на сервере, обновим проект, выполнив commit и push в репозиторий на GitHub:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Затем выполним pull на сервере. И вуаля, Composer теперь у вас в проекте! Но помните, он доступен только внутри проекта и запускать его нужно командой composer.phar.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Продолжаем работу с Composer"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На некоторых хостингах, включая хостинг beget, требуется явно указывать версию PHP для Composer. Это связано с тем, что версия PHP в консоли может отличаться от версии, заданной в контрольной панели для сайта. Это как если бы у вас было два разных языка, один для общения с друзьями, и другой - для работы. И Composer должен знать, на каком языке ему говорить.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вы можете узнать текущую версию PHP для консоли с помощью команды php -v. Спросим у PHP: \"Сколько тебе лет?\".

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если на сервере установлено несколько версий PHP, то в команде нужно указывать конкретную версию PHP. В моём случае это php8.3.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Команда php8.3 composer install обновит зависимости в вашем проекте. Однако, эта команда будет работать только в том случае, если php8.3 и composer доступны в вашем PATH. Это означает, что оболочка может найти их, когда вы вводите эти команды.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если php8.3 или composer не найдены, вам придется указать полный путь до этих программ. Например, если composer находится в /usr/local/bin/composer, вы должны использовать этот полный путь в команде.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Алиас php8.3 обычно создается при установке этой версии PHP. Это делается для удобства, чтобы можно было легко переключаться между разными версиями PHP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Выполним:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php8.3 composer install","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По этой команде все необходимые нашему приложению пакеты Composer скачает и установит, вот такой он молодец.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Пару слов о composer.lock"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В проектах, которые используют Composer, есть важный служебный файл под названием composer.lock. Этот файл выполняет несколько ключевых функций:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Фиксация версий зависимостей: когда вы впервые устанавливаете зависимости с помощью команды composer install, Composer смотрит на файл composer.json вашего проекта, чтобы узнать, какие пакеты ему нужны, и скачивает их. Затем Composer записывает точные версии установленных пакетов в composer.lock. Это означает, что если вы или кто-то другой запустите composer install в будущем, Composer будет использовать версии из composer.lock, а не искать новые версии. Это гарантирует, что все, кто работает над проектом, используют одни и те же версии пакетов, что уменьшает вероятность проблем совместимости.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Безопасность: файл composer.lock также содержит хеши каждого пакета. Это позволяет Composer проверить, что пакеты не были изменены или повреждены с момента их установки.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ускорение установки: файл composer.lock содержит точные версии и источники каждого пакета, поэтому Composer быстрее устанавливает пакеты, не тратя время на их поиск.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Важно помнить, что composer.lock должен быть включен в ваш репозиторий кода, чтобы все, кто работает над проектом, могли использовать одни и те же версии пакетов. Если вы хотите обновить версии пакетов, вы можете использовать composer update, который обновит composer.lock.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Создаём базу данных для приложения"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создание базы данных для приложения - это простой процесс. Все, что вам нужно, это найти раздел \"Базы данных\" в панели управления вашего хостинга.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И последний шаг - запишите имя базы данных, пароль к ней и имя пользователя. Это важная информация, которую нужно хранить в безопасном месте.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Настраиваем .env"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Файл .env очень важен для работы проекта на Laravel. Этот файл содержит различные настройки окружения, такие как данные для подключения к базе данных, ключи API и другую конфиденциальную информацию, которая также зависит от окружения. .env на продакшене один, у вас локально свой, у коллег по команде другой. Именно поэтому файл .env не присутствует в git-репозитоиях.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Создадим файл .env из шаблона (.env.example) командой:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"cp .env.example .env","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот он появился:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Еще один способ просмотреть содержимое текущей директории - это использовать команду ls -la. Эта команда отображает все файлы и папки в текущей директории, включая скрытые файлы (те, что начинаются с точки). Выполняем:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"ls -la","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В консоли вы увидите список всех файлов и папок в текущей директории.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Итак, файл .env создан, редактируем его! Первым делом займемся APP_KEY. Это ключ безопасности, который очень важен для Laravel, он используется для шифрования данных пользователя, таких как сессии, куки и пароли, а также для защиты от CSRF атак. Без этого ключа ваше приложение Laravel не сможет правильно функционировать.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Надо быть аккуратными при работе с APP_KEY, ведь если заново сгенерировать ключ на уже рабочем проекте, то все ранее зашифрованные данные (например, пароли пользователей) станут недоступными!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Ключ генерируется командой:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php8.3 artisan key:generate","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь в файле .env появился новый ключ безопасности в строке APP_KEY.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Кроме этого в .env надо установить еще несколько параметров:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["APP_ENV=production указываем что режим работы приложения - продакшен","APP_URL=\"домен приложения\"","DB_DATABASE=\"имя БД\"","DB_USERNAME=\"имя пользователя БД\"","DB_PASSWORD=\"пароль от БД\""],"type":"UL"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Миграции"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Миграции в Laravel - это очень удобный способ управления структурой вашей базы данных. Команда php8.3 artisan migrate запускает все ваши миграции, что, по сути, “накатывает” изменения на вашу базу данных. А при первом вызове создает структуру базы данных с нуля.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Поскольку вы указали APP_ENV=production в файле .env, Laravel выдает предупреждение перед запуском миграций. Это сделано для того, чтобы предотвратить случайные изменения в вашей продакшен-базе данных. Это двойная проверка перед тем, как вы вносите какие-либо изменения.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Рестарт очередей и компиляция ассетов"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Рестарт очередей выполняется с помощью команды:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php artisan queue:restart","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Компиляция ассетов - это процесс, в котором Laravel собирает и оптимизирует ваши CSS и JavaScript файлы. \"Ассеты сбилдились\" (\"ассеты собраны\", стили и скрипты скомпилированы) в отношении Laravel обычно означает, что ваши статические файлы, такие как CSS, JavaScript и изображения, были обработаны и оптимизированы.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Это делается с помощью инструмента под названием Laravel Mix или Vite. Для компиляции ассетов необходим NPM (Node Package Manager) - менеджер пакетов для JavaScript. Ассеты билдятся с помощью команды:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"npm run build","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Некоторые shared хостинги поддерживают Node.js и npm, но не все. Важно проверить у вашего провайдера хостинга - есть ли на нем npm.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Что делать есть shared-хостинг не поддерживает npm?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Мы можем скомпилировать стили в локальном проекте и через GitHub отправить на сервер. Вот как это сделать:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Скомпилируйте ваши стили. Используйте команду npm run build.","Убедитесь, что папки с css и js добавлены в индекс, при необходимости добавьте. Используйте команду git add public/css или git add public/js (или обе команды, если вы храните стили в обоих местах) для добавления скомпилированных файлов в локальный репозиторий Git.","Сделайте коммит.","Запушьте изменения на GitHub.","Склонируйте изменения с GitHub на сервер."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Перед следующим коммитом не забудьте сбилдить ассеты!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Проверяем работу"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Всё готово для проверки работы. Обновляем страницу и видим наше приложение на Laravel.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Всё настроено. Как теперь обновлять проект?"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1. Отправляем изменения в удалённый репозиторий на GitHub.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1.1. Создаём коммит в локальном репозитории:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"commit \"описание коммита\"","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1.2. Отправляем изменения в GitHub:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git push","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1.3. Проверяем что репозиторий на GitHub обновлён.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2. Теперь заберем изменения из GitHub-репозитория на сервер.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2.1. Переходим в командную строку на сервере. И выполняем:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"git pull","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2.2. Устанавливаем зависимости, если вдруг какие-то пакеты новые подключали:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php8.3 composer install","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2.3. Накатываем миграции, собираем ассеты и делаем рестарт очередей:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"php8.3 artisan migrate npm run prod php artisan queue:restart","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Выводы"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вот так мы практически с нуля настроили деплой проекта на сервер. Примерно это займет около 30 минут. Каждый дальнейший деплой - 3-5 минут. Не очень радостные перспективы для проектов, на которых необходимо часто обновлять код на продакшене. Также не стоит забывать про человеческий фактор, много рутинной работы - можно забыть выполнить какой-то шаг - не обновить зависимости, забыть про миграции, компиляцию ассетов или рестарт очередей. Легко пропустить какую-то команду, а любая оплошность приведет к ошибке при деплое, и нужно будет тратить время на поиск причины.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Часть 2. VPS и настройка окружения

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данил Щуцкий, автор проекта CutCode.

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":1,"favorites":1,"reposts":0,"views":732,"hits":1171,"reads":null,"online":0},"dateFavorite":0,"hitsCount":1171,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1290754-gaid-po-deployu-web-prilozhenii-dlya-novichkov-chast-1-shared-hosting","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1166211,"customUri":null,"subsiteId":2317176,"title":"🌙 Вышел релиз MoonShine v.2.13.0 с кодовым именем \"Red Velvet\"!🌙","date":1715258677,"dateModified":1715258677,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Напомню, что MoonShine это open-source админ-панель для проектов на Laravel.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Давайте взглянем на самое интересное в этом обновлении!

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Возможноcть изменить query builder у HasMany

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"HasMany::make()->modifyBuilder(fn(Builder $q) => $q->where('active', 1));","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Улучшения для BelongsTo/BelongsToMany полей

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Получение значений для BelongsTo/BelongsToMany полей приведено к единому виду и теперь для них всегда доступен valuesQuery и при большом запросе вы сможете самостоятельно его оптимизировать

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"BelongsTo::make(\n __('moonshine::ui.resource.role'),\n 'moonshineUserRole',\n formatted: static fn (MoonshineUserRole $model) => $model->name,\n resource: MoonShineUserRoleResource::class,\n)\n ->valuesQuery(fn(Builder $q) => $q->select(['id', 'name']))","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Обновилось поле MoonShine Layouts

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Исправлена проблема ререндера предыдущих шаблонов","Исправлена проблема исключения декораций"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Еще ряд изменений:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Логотип на 404 странице теперь выводится из конфига и если есть желание вывести на 404 логотип отличающийся от дефолтного, то воспользуйтесь ключом logo404","Исправили реактивность внутри HasOne/HasMany","Исправили morphMap для морф полей","Исправили удаление файла при замене и удалении"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сайт MoonShine - https://moonshine-laravel.com

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":0,"reposts":0,"views":474,"hits":90,"reads":null,"online":0},"dateFavorite":0,"hitsCount":90,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1166211-vyshel-reliz-moonshine-v2130-s-kodovym-imenem-red-velvet","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}},{"type":"entry","data":{"id":1161261,"customUri":null,"subsiteId":2317176,"title":"PHP и Laravel дайджест новостей за апрель 2024 года","date":1715011791,"dateModified":1715011791,"blocks":[{"type":"text","cover":true,"hidden":false,"anchor":"","data":{"text":"

Всем привет! Это PHP Дайджест от CutCode. Давайте посмотрим, что произошло за прошедший месяц в мире PHP.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Новости"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вышли PHP 8.1.28, PHP 8.2.18 и PHP 8.3.6

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

❗В этих выпусках исправлены уязвимости CVE-2024-1874, CVE-2024-2756 и CVE-2024-3096, в PHP 8.3.6 также исправлена уязвимость CVE-2024-2757, которые исправляют:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Обход функции proc_open в Windows с экранированием аргументов для bat- и cmd-файлов","Обход куки __Host / __Secure","Бесконечный цикл в функции mb_encode_mimeheader","Обработка функцией password_hash значения $password с нулевым символом."],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

❗Версия PHP 8.3.5 была пропущена, пожалуйста, не полагайтесь этот тег.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Заявление PHP об уязвимости в glibc/iconv

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Шумиха вокруг CVE-2024-2961, связанная с PHP, была крайне преувеличена. У многих сложилось впечатление, что уязвимость существует в самом языке и оказывает огромное влияние на PHP-разработчиков. Однако это не так.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Не стоит ожидать выпуска патча от PHP, поскольку glibc является динамически подключаемой библиотекой и не компилируется вместе с интерпретатором. Обновления glibc будет достаточно.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Все, что нужно знать о бэкдоре в XZ

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если вы не следили за этой историей, то вот вкратце, что произошло.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Некто под GitHub-аккаунтом @JiaT75 в течение 2 лет вносил свой вклад в библиетеку liblzma, создавая SSH-бэкдор незаметно для других сопровождающих. Он сделал более 700 коммитов, из которых лишь небольшое количество было вредоносным и спрятано в тестовых файлах. Странное поведение было случайно обнаружено при проведении микробенчмаркинга утилиты xz.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Скорее всего, эта атака не является единичным случаем. По крайней мере, OpenJS Foundation уже сообщал о неудачных попытках захвата их проектов.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Объединение усилий для разработки стандартов кибербезопасности с открытым исходным кодом

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PHP Foundation будет сотрудничать с фондами Apache, Eclipse, Rust и Python для создания стандартов для закона о киберустойчивости.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вышел PhpStorm 2024.1

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Главные новинки этой версии:

"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["Автодополнение строки целиком с помощью локального ИИ","Поддержка Symfony AssetMapper","Новый терминал","Улучшения для Pest","Поддержка PHPUnit 11.0"],"type":"UL"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Большинство новостей ядра PHP подробно освещаются в серии PHP Core Roundup от PHP Foundation, мы лишь быстро по ним пробежимся:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

RFC: Property hooks

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хуки стали одним из самых больших и обсуждаемых RFC, в одном из предыдущих выпусках мы о них уже упоминали.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Они позволят разработчикам переопределять стандартное поведение \"get\" и \"set\" свойств объекта. Ларри Гарфилд и Илия Товилло вдохновлялись языками Kotlin, C# и Swift при разработке этого RFC.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

RFC был принят подавляющим большинством голосов и мы с нетерпением ждем хуки в PHP 8.4.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣RFC: new MyClass()->method() without parentheses

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

RFC Валентина Удальцова, о котором мы говорили в конце прошлого года, перешел в стадию обсуждения.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

У RFC положительные отзывы, ждем начала голосования, один голос у Валентина уже точно есть :)

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На PHP-викторине, о которой мы поговорим чуть позже, Валентин также рассказал о своем RFC.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣RFC: array_find

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Джошуа Рюсвег (Joshua Rüsweg) предлагает добавить новую функцию для поиска первого элемента, для которого callback-функция возвращает значение true.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

📣RFC: Casing of acronyms in class and method names

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Тим Дюстерхус (Tim Düsterhus) предлагает пересмотреть предыдущее решение RFC по именованию классов. Вместо того, чтобы относиться к аббревиатурам как к обычным словам, сделать имена классов согласованными с PascalCase.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

RFC: Deprecate GET/POST sessions

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

RFC о котором мы говорили в прошлом выпуске принят.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сейчас PHP поддерживает два способа распространения идентификатора токена сессии: с помощью файлов cookie и с помощью параметров GET или POST.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В PHP 8.4, если параметр session.use_only_cookies отключен, а параметр session.use_trans_sid – включен, будет выдаваться предупреждение об устаревании.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В PHP 9.0 распространение идентификатора токена сессии с помощью параметров GET или POST будет удалено.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

RFC: Release cycle update

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Поддержка безопасности для версий PHP увеличена на один год. Таким образом, каждая версия PHP будет поддерживаться 4 года: 2 года исправлений ошибок и 2 года исправлений безопасности.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Изменения применяются немедленно ко всем поддерживаемым в настоящее время веткам, а ветка PHP 8.1 получит дополнительный год исправлений безопасности.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Релиз-менеджеры PHP 8.4

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

По традиции, PHP 8.4 будут сопровождать два «новичка» релиз-менеджера: Саки Такамачи (Saki Takamachi), core-разработчик, поддерживаемая PHP Foundation, и Кельвин Бакли (Calvin Buckley), разработчик нескольких модулей PECL.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Им будет помогать ветеран релиз-менеджер PHP 8.3 Эрик Манн (Eric Mann).

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

На канале CutCode прошла вторая викторина «Своя игра» по PHP

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Валентин Удальцов, Алексей Гагарин и Петр Мязин ответили на каверзные вопросы по PHP.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Состоялся релиз onFriday

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Сервис от CutCode по деплою приложений в один клик с нулевым временем простоя и контролем результата. Возможности аналогичные Envoyer, плюс есть дополнительные фичи. Подходит для деплоя приложений на PHP и Go.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Laravel дайджест"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Обновления Laravel"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.2. Add fluent helper

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50848

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR на тему Fluent объекта. Что за Fluent такой? Это дополнительный сахар. Там где у нас коллекции не справляются с сахаром, на помощь приходит Fluent объект. Давайте взглянем на примеры. Скажем, у нас есть коллекция.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Есть метод Get. Элемент массива с Fluent мы можем просто указывать как свойство объекта и получать также доступ:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$data = [\n 'user' => [\n 'name' => 'Philo',\n 'address' => [\n 'city' => 'Amsterdam',\n 'country' => 'Netherlands',\n ] \n ],\n 'posts' => [\n [\n 'title' => 'Post 1', \n ],\n [\n 'title' => 'Post 2', \n ]\n ]\n];\n\n❌ collect($data)->get('user');\n✅ fluent($data)->user","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Далее у нас в коллекциях через метод Get не поддерживается вложенность через метод DataGet во Fluent объекте. Теперь такая возможность есть, через точку можем обращаться к вложенным элементам:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"❌ collect($data)->get('user')['name'];\n✅ fluent($data)->get('user.name');","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Также есть возможность Fluent объект трансформировать в коллекции:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"❌ collect(collect($data)->get('posts'))->pluck('title');\n✅ fluent($data)->collect('posts')->pluck('title');","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И присутствует интересный метод Scope, который у нас под капотом содержит метод get, но возвращает новый instance fluent-объекта. И также нам демонстрирует пример, как удобно с помощью fluent-helper сделать JSON-Encode.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"❌ json_encode(collect($data)->get('user')['address']);\n✅ fluent($data)->scope('user.address')->toJson();","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.2. Add a new helper for context

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50878

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий PR затрагивает context. У нас уже был context фасад. И пришло время для helper для функции context:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"context(['user' => auth()->user()]); // Add user information to the context\n$context = context(); // Retrieve the context object\n$user = context('user'); // Retrieve user information from the context","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.2. Add default value for get and getHidden on Context

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50824

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Еще один PR по Context . У нас были методы-геттеры для получения элементов из контекста get и get-hidden. Но при этом мы не могли с вами указать дефолтное значение. Если такого ключа в контексте нет, то мы получаем с вами null:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$isUser = Context::get('is_user'); // If is not has in array return null forever\n$isUser = Context::getHidden('is_user'); // If is not has in array return null forever","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Не совсем привычно для гетеров в Laravel, так как всегда присутствует вторым параметром дефолтное значение, и теперь оно есть и в Context:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$isUser = Context::get('is_user', false); // If is not has in array return false\n$isUser = Context::getHidden('is_user', false); // If is not has in array return false","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.2. Trim invisible characters"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50832

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR затрагивает Middlewar trim strings. До этого была проблема, невидимые Unicode символы Trimstrings не чистил. PR решает эту проблему.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.2. Do not wipe database if it does not exists"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50838

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий PR по консольным командам. Проблема заключается в следующем. Если вы сделаете Migrate fresh и у вас еще не создана база данных, помощник вам подскажет создать её либо нет. И если вы отвечаете нет, то сработает исключение и вы получите ошибку.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь мы не будем получать исключения, а команда просто не будет выполнена.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"11.2. Str trim methods"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50822

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий PR добавляет новый сахар в класс по работе со строками. Раньше был trim(), теперь есть ltrim() и rtrim().

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.3. Add Component: Multiline Text Input

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/prompts/pull/88

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Новая фича в Laravel Prompts. Обожаю этот пакет, и теперь появился элемент формы textarea() с помощью которого можем в консоли пользоваться текстовым блоком.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.3. Add pull methods to Context

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50904

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Этот PR добавляет в Context новый метод pull и pullHidden. Как и во всех pull в Laravel мы забираем значение из указанного элемента и после удаляем его из Context.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.3. Add session hasAny method

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50897

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В объект сессий добавили метод hasAny, чтобы не плодить условия или и указать все в массиве через новый метод:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

До:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"if (session()->has('first_name') || session()->has('last_name')) {\n // do something...\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

После

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"if (session()->hasAny(['first_name', 'last_name'])) {\n // do something...\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.4. Introduces Exceptions facade

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50704

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR от Nuno Maduro, новый фасад exceptions, который даст нам новые возможности в тестировании exception. В примерах показано, как в тестах мы можем переключить exceptions в fake и далее в тестах проверять, вызваны ли исключения, сколько раз вызваны, и так далее.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.4. Allowing Usage of Livewire Wire Boolean Style Directives

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51007

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий pull request, для фанатов livewire. Теперь в Blade-директивы можно будет отправлять параметры в boolean стиле. До этого при генерации HTML у нас добавлялся value:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"{{-- Using like this --}}\n\n{{-- Generates this HTML --}}\n
","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь же у нас если нет значения, то и при рендере его не будет:

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.4. afterQuery hook

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/50587

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий pull request добавляет новый метод в queryBuilder — autoquery, который исходя из названия будет вызван после того, как будет вызван запрос.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В pull request показан пример, где есть scope и мы после запроса итерируем продукты и устанавливаем определенный атрибут.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"public function scopeWithIsFavoriteOf($query, ?User $user = null) : void\n{\n if ($user === null) {\n return $query;\n }\n $query->addSelect([\n // 'is_favorite' => some query ...\n]);\n\n}\n$products = Product::withIsFavoriteOf(auth()->user())->get();\nif (auth()->user() === null) {\n$products->each->setAttribute('is_favorite', false);\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь же с помощью этого метода мы прямо в scope можем указать что у нас за логика будет после вызова запроса и здесь же ее и выполнить:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"public function scopeWithIsFavoriteOf($query, ?User $user = null) : void\n{\n if ($user === null) {\n $query->afterQuery(fn ($products) => $products->each->setAttribute('is_favorite', false));\n return;\n }\n $query->addSelect([\n // 'is_favorite' => some query ...\n ]);\n}\nProduct::withIsFavoriteOf(auth()->user())->get();","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.4. Add RequiredIfDeclined validation rule

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51030

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Следующий pull request добавляет правила валидации requiredIfDeclined. У нас уже был requiredIfAccepted, теперь появился его “брат” - если у нас checkbox не выбран.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.4. Adds support for enums on mapInto collection method

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51027

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В следующем pull request речь пойдет методе mapInto для коллекций. Теперь мы можем мапить в enum данные:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"public function store(Request $request)\n{\n $request->validate([\n 'features' => ['array'],\n 'features.*' => [new Enum(Feature::class)],\n ]);\n$features = $request\n ->collect('features')\n ->mapInto(Feature::class);\n\nif ($features->contains(Feature::DarkMode)) {\n // 🌙\n}\n\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.4. Adds Reversible Forms to Prompts

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/prompts/pull/118

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В Laravel Prompts добавилась новая фича. До этого мы могли через Form Builder, через Helper Form, строить формы и получать значение из всех элементов формы только после сабмита:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$responses = form()\n ->text('What is your name?')\n ->select('What is your favourite language?', ['PHP', 'JS'])\n ->submit();","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

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

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$responses = form()\n ->text('What is your name?')\n ->select('What is your favourite language?', ['PHP', 'JS'])\n ->add(fn ($responses) => note(\"Your name is {$responses[0]} and your language is {$responses[1]}\"))\n ->submit();","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.5. Supercharge Blade

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51143

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR, который затрагивает Blade, и ускоряет его работу. Теперь Blade будет работать в 15-25 раз быстрее.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.5. Blade Component Loop Speed Improvement

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51158

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PR оптимизирующий loop внутри Blade - скорость увеличена на 7%.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

11.5. Ability to generate URL's with query params

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

https://github.com/laravel/framework/pull/51075

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

PullRequest, который затрагивает URL-генератор. Появился новый метод Query, с помощью которого можно удобно строить URL вместе с Query-параметрами:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"// http://localhost/products?sort=-name\nurl()->query('products', ['sort' => '-name']);\n// http://localhost/products?columns[0]=name&columns[1]=price&columns[2]=quantity\nurl()->query('products', ['columns' => ['name', 'price', 'quantity']]);","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Много возможностей, как видим из примеров. Крутая фича для URL генератора.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Видео-версия дайджеста:

"}},{"type":"video","cover":false,"hidden":false,"anchor":"","data":{"title":"","video":{"type":"video","data":{"thumbnail":{"type":"image","data":{"uuid":"17880372-6f02-571a-a76f-464f659bc6b1","width":1280,"height":720,"size":186403,"type":"jpg","color":"1a4658","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQCAwMDAgQDAwMEBAQEBQkGBQUFBQsICAYJDQsNDQ0LDAwOEBQRDg8TDwwMEhgSExUWFxcXDhEZGxkWGhQWFxb/2wBDAQQEBAUFBQoGBgoWDwwPFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhb/wAARCAAKAAoDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAwUGB//EACUQAAICAQIEBwAAAAAAAAAAAAEEAgMFABESIjFhBhMWIVFSgf/EABUBAQEAAAAAAAAAAAAAAAAAAAMH/8QAIBEAAQMEAgMAAAAAAAAAAAAAAQIDBAAiMTIGEiFBQv/aAAwDAQACEQMRAD8Aa5nw2vl1L51K2qWv2VB1CSkYeZGuXEBEnqO40ehTFV0Qh6FX5Ygc+Po4v3266z2xlkUr7MWjaiO20z8als1kHxmWwHmQAxPYC2X2PfQPxob5T3C7QBsMDHzVhicdEZssoctKlK1GVHz7r//Z"}},"width":800,"height":450,"time":0,"external_service":{"name":"youtube","id":"nxo90UW_UpU"}}}}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":0,"reposts":0,"views":935,"hits":170,"reads":null,"online":0},"dateFavorite":0,"hitsCount":170,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1161261-php-i-laravel-daidzhest-novostei-za-aprel-2024-goda","author":{"id":2317176,"name":"Данил Щуцкий","nickname":null,"description":"Backend PHP Developer. CutCodeFather (YouTube channel and Laravel community). Creator of MoonShine admin panel","uri":"","avatar":{"type":"image","data":{"uuid":"98eeb543-31bb-5d9c-9b44-0ea00c218511","width":640,"height":640,"size":11794,"type":"jpg","color":"7c3ceb","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":3148186,"userId":2317176,"count":0,"shareImage":"https://api.vc.ru/achievements/share/3148186"}],"lastModificationDate":1765736522,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":2}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null}}],"cursor":"PuR2GsZKFTvhhGtQA4/g7sfbBa8hRyGI6YitsrXO+VvHeLfWP7Kpxkhb42tDnCJg","isAnonymized":true}};