Книга «Laravel. Полное руководство. 2-е издание»

    image Привет, Хаброжители! Что отличает Laravel от других PHP-фреймворков? Скорость и простота. Стремительная разработка приложений, обширная экосистема и набор инструментов Laravel позволяют быстро создавать сайты и приложения, отличающиеся чистым удобочитаемым кодом.

    Мэтт Стаффер, известный преподаватель и ведущий разработчик, предлагает как общий обзор фреймворка, так и конкретные примеры работы с ним. Опытным PHP-разработчикам книга поможет быстро войти в новую тему, чтобы реализовать проект на Laravel. В издании также раскрыты темы Laravel Dusk и Horizon, собрана информация о ресурсах сообщества и других пакетах, не входящих в ядро Laravel. В этой книге вы найдете: • Инструменты для сбора, проверки, нормализации, фильтрации данных пользователя • Blade, мощный пользовательский шаблонизатор Laravel • Выразительная модель Eloquent ORM для работы с базами данных приложений • Информация о роли объекта Illuminate Request в жизненном цикле приложения • PHPUnit, Mockery и Dusk для тестирования вашего PHP-кода • Инструменты для написания JSON и RESTful API • Интерфейсы для доступа к файловой системе, сессиям, куки, кэшам и поиску • Реализации очередей, заданий, событий и публикации событий WebSocket

    Движок шаблонов Blade


    PHP в качестве языка шаблонов функционирует относительно хорошо. Но у него есть свои недостатки, поэтому нельзя использовать <?php inline повсеместно. В большинстве современных фреймворков есть свой язык шаблонов.

    Laravel предлагает собственный движок шаблонов Blade, созданный на основе движка .NET Razor. Он обладает лаконичным синтаксисом, довольно понятен, сопровождается мощной и интуитивно понятной моделью наследования и легкой расширяемостью.

    Быстро ознакомиться с тем, как выглядит Blade, можно на примере 4.1.

    Пример 4.1. Примеры Blade

    <h1>{{ $group->title }}</h1>
    {!! $group->heroImageHtml() !!}
    
    @forelse ($users as $user)
           • {{ $user->first_name }} {{ $user->last_name }}<br>
    @empty
           No users in this group.
    @endforelse

    Как видно, в коде Blade используются фигурные скобки для Echo и соглашение, в котором его пользовательские теги, называемые «директивами», имеют префикс @. Вы будете применять директивы для всех своих структур управления, а также для наследования и любых пользовательских функций, которые хотите добавить.

    Синтаксис Blade чистый и лаконичный, поэтому работать с ним проще и приятнее, чем с альтернативами. Но в тот момент, когда понадобится что-нибудь сложное в ваших шаблонах — вложенное наследование, сложные условия или рекурсия, — движок проявляет себя с лучшей стороны. Как и лучшие компоненты Laravel, тяжелые требования к приложениям он упрощает и делает доступными.

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

    Отображение данных


    Как вы можете видеть в примере 4.1, скобки {{ и }} используются для обертки PHP-кода, который вы хотели бы отобразить. Код {{ $variable }} действует подобно <?= $variable ?> в простом PHP.

    Однако есть отличие: Blade по умолчанию экранирует все отображения PHP-функцией htmlentities() для защиты ваших пользователей от вставки вредоносных сценариев. Это означает, что {{ $variable }} функционально эквивалентно <?= htmlentities($variable) ?>. Если вы не хотите экранировать вывод, используйте {!!! и !!} вместо этого.
    СКОБКИ {{ И }} ПРИ ИСПОЛЬЗОВАНИИ ФРОНТЕНД-ФРЕЙМВОРКА ШАБЛОНИЗАЦИИ

    Вы могли заметить, что синтаксис отображения для Blade ({{}}) аналогичен таковым для многих фронтенд-фреймворков. Как же Laravel узнает, используете вы Blade или Handlebars?

    Blade игнорирует все {{ с предваряющим знаком @. Таким образом, он проанализирует первый из следующих примеров, но второй будет выведен полностью:

    // Распознается как Blade; значение $bladeVariable
    // отображается в представлении
    {{ $bladeVariable }}

    // @ удаляется и "{{ handlebarsVariable }}" полностью
    // отображается в представлении
    @{{ handlebarsVariable }}

    Вы также можете обернуть любые большие части содержимого сценария директивой verbatim (http://bit.ly/2OnrPRP).

    Управляющие структуры


    Большинство управляющих структур в Blade будут знакомы. Многие напрямую дублируют имя и структуру такого же тега в PHP.

    Есть несколько хелперов для удобства, но в целом структуры управления выглядят чище, чем в PHP.

    Условные конструкции


    Рассмотрим логические структуры управления.

    @if

    Выражение @if ($condition) в Blade компилируется в <?php if ($condition): ?>. else, elseif и endif — с точно таким же стилем синтаксиса в PHP. Взгляните на пример 4.2

    Пример 4.2. @if, else, elseif и endif

    @if (count($talks) === 1)
         There is one talk at this time period.
    @elseif (count($talks) === 0)
         There are no talks at this time period.
    @else
         There are {{ count($talks) }} talks at this time period.
    @endif

    Как и в случае с собственными условными выражениями PHP, вы можете смешивать и комбинировать их так, как вам удобно. У них нет особой логики; есть анализатор с поиском в форме @if($condition) и заменой соответствующим кодом PHP.

    @unless и @endunless

    @unless, с другой стороны, — это новый синтаксис, который не имеет прямого эквивалента в PHP. Это противоположность @if. @unless($condition) совпадает с <?php if (! $condition). Вы можете увидеть это на примере 4.3.

    Пример 4.3. @unless и @endunless

    @unless ($user->hasPaid())
           You can complete your payment by switching to the payment tab.
    @endunless

    Циклы


    Далее рассмотрим циклы.

    @for, @foreach и @while

    for, foreach и while работают в Blade так же, как и в PHP (примеры 4.4–4.6).

    Пример 4.4. for и @endfor

    @for ($i = 0; $i < $talk->slotsCount(); $i++)
          The number is {{ $i }}<br>
    @endfor

    Пример 4.5. foreach и @endforeach

    @foreach ($talks as $talk)
           • {{ $talk->title }} ({{ $talk->length }} minutes)<br>
    @endforeach

    Пример 4.6. while и @endwhile

    @while ($item = array_pop($items))
           {{ $item->orSomething() }}<br>
    @endwhile

    @forelse и @endforelse

    forelse — это foreach, который может быть выполнен, даже если перебираемый вами объект пуст. Мы видели это в действии в начале главы. Пример 4.7 показывает другой вариант.

    Пример 4.7. forelse

    @forelse ($talks as $talk)
           • {{ $talk->title }} ({{ $talk->length }} minutes)<br>
    @empty
           No talks this day.
    @endforelse

    ПЕРЕМЕННАЯ $LOOP В ДИРЕКТИВАХ FOREACH И @FORELSE

    Директивы foreach и forelse (представленные в Laravel 5.3) добавляют переменную $loop, недоступную в циклах foreach PHP. При использовании внутри цикла foreach или forelse она будет возвращать объект stdClass со следующими свойствами.

    • index — отсчитанный от 0 индекс текущего элемента в цикле; 0 — «первый элемент».

    • iteration — отсчитанный от 1 индекс текущего элемента в цикле; 1 — «первый элемент».

    • remaining — количество элементов, оставшихся в цикле.

    • count — количество элементов в цикле.

    • first — логическое значение, указывающее, является ли данный элемент первым элементом в цикле.

    • last — логическое значение, указывающее, является ли данный элемент последним элементом в цикле.

    • depth — сколько «уровней» в этом цикле: 1 для цикла, 2 для цикла внутри цикла и т. д.

    • parent — ссылка на переменную $loop для элемента родительского цикла, если этот цикл находится в другом цикле foreach; иначе null.

    Вот пример того, как это работает:

    <ul>
    @foreach ($pages as $page)
           <li>{{ $loop->iteration }}: {{ $page->title }}
                 @if ($page->hasChildren())
                 <ul>
                 @foreach ($page->children() as $child)
                 <li>{{ $loop->parent->iteration }}
                       .{{ $loop->iteration }}:
                       {{ $child->title }}</li>
                 @endforeach
                 </ul>
                 @endif
           </li>
    @endforeach
    </ul>

    Наследование шаблонов


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

    Посмотрим, как наследование структурируется с Blade.

    Определение разделов страницы с помощью директив @section/@show и yield


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

    Пример 4.8. Структура Blade

    <!-- resources/views/layouts/master.blade.php -->
    <html>
           <head>
                  <title>My Site | @yield('title', 'Home Page')</title>
           </head>
           <body>
                  <div class="container">
                        @yield('content')
                  </div>
                  @section('footerScripts')
                         <script src="app.js"></script>
                  @show
           </body>
    </html>

    Это напоминает обычную HTML-страницу, но вы можете видеть yield в двух местах (title и content), и мы определили section в третьем (footerScripts). Здесь у нас есть три директивы Blade: только yield('content'), yield('title', 'Home Page') с заданным по умолчанию значением и @section/@show с реальным содержимым в нем.

    Хотя они выглядят немного по-разному, но функционируют, по существу, одинаково. Все три определяют, что есть раздел с заданным именем (первый параметр), который может быть расширен позже, и что делать, если раздел не был расширен. Они делают это либо строкой возврата ('Home Page'), либо без возврата (просто не будет отображаться ничего, если директива не расширена), либо с возвратом всего блока (в данном случае ).

    В чем разница? У yield('content') нет контента по умолчанию. Кроме того, в yield('title') оно будет отображаться, только если директива не расширяется. В обратном случае ее дочерние разделы не будут иметь программного доступа к значению по умолчанию. @section/@show одновременно определяет значение по умолчанию и делает так, чтобы его содержимое было доступно его дочерним элементам через @parent.

    Если у вас есть такой родительский макет, вы можете расширить его в новом файле шаблона, как в примере 4.9.

    Пример 4.9. Расширение макета Blade

    <!-- resources/views/dashboard.blade.php -->
    @extends('layouts.master')
    
    @section('title', 'Dashboard')
    
    @section('content')
           Welcome to your application dashboard!
    @endsection
    
    @section('footerScripts')
          @parent
          <script src="dashboard.js"></script>
    @endsection

    Это дочернее представление позволяет нам показать несколько новых концепций наследования Blade.

    @extends

    В примере 4.9 с помощью @extends('layouts.master') мы определяем, что это представление не должно отображаться само по себе, а вместо этого расширяет другое представление. Это означает, что его роль заключается в установке содержания различных разделов, но не в работе в одиночку. Больше похоже на серию блоков контента, чем на страницу HTML. Строка также определяет, что представление, которое она расширяет, находится по адресу resources/views/layouts/master.blade.php.

    Каждый файл должен расширять только один другой файл, и вызов @extends обязан быть первой строкой файла.

    @section и @endsection

    С помощью @section('title', 'Dashboard') мы предоставляем наш контент для первого раздела, title. Поскольку содержимое очень короткое, вместо @section и @endsection мы используем сокращенную форму. Это позволяет передавать содержимое как второй параметр @section, а затем двигаться дальше. Если вас немного сбивает с толку @section без @endsection, можно использовать обычный синтаксис.

    С @section('content') и далее мы используем обычный синтаксис для определения содержимого раздела content. Пока мы вставим небольшое приветствие. Однако заметьте: когда вы применяете @section в дочернем представлении, вы заканчиваете его @endsection (или его псевдонимом stop) вместо show, который зарезервирован для определения разделов в родительских представлениях.

    @parent

    С @section('footerScripts') и далее мы используем обычный синтаксис для определения содержимого раздела footerScripts.

    Помните, что мы фактически определили этот контент (или по крайней мере его «значение по умолчанию») уже в главном макете. Так что на этот раз у нас есть два варианта: либо перезаписать содержимое из родительского представления, либо добавить к нему.
    Вы можете видеть, что у нас есть возможность включить содержимое из родительского представления с помощью директивы @parent внутри этого раздела. В противном случае содержимое раздела будет полностью переписано всем тем, что определено в родителе этого раздела.

    Включение составляющих представления


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

    @include

    Что, если мы находимся в одном представлении и хотим использовать другое? Возможно, есть кнопка регистрации, которую желательно повторно добавить по всему сайту. И, может быть, хочется подбирать текст кнопки каждый раз, когда ее используем. Посмотрите на пример 4.10.

    Пример 4.10. Включение составляющих представления с include

    <!-- resources/views/home.blade.php -->
    <div class="content" data-page-name="{{ $pageName }}">
          <p>Here's why you should sign up for our app: <strong>It's Great.</strong></p>
    
          @include('sign-up-button', ['text' => 'See just how great it is'])
    </div>
    
    <!-- resources/views/sign-up-button.blade.php -->
    <a class="button button--callout" data-page-name="{{ $pageName }}">
          <i class="exclamation-icon"></i> {{ $text }}
    </a>

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

    Вы также можете использовать директивы includeIf, includeWhen и includeFirst, как показано в примере 4.11.

    Пример 4.11. Включение представлений по условиям

    {{-- Включить представление, если оно существует --}}
    @includeIf('sidebars.admin', ['some' => 'data'])
    
    {{-- Включить представление, если переданная переменная равна true --}}
    @includeWhen($user->isAdmin(), 'sidebars.admin', ['some' => 'data'])
    
    {{-- Включить первое представление из данного массива представлений --}}
    @includeFirst(['customs.header', 'header'], ['some' => 'data'])
    

    @each

    Вы можете представить себе обстоятельства, когда вам нужно перебрать массив, коллекцию и include часть для каждого элемента. Для этого есть директива each.

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

    Пример 4.12. Использование составляющих представления в цикле с each

    <!-- resources/views/sidebar.blade.php -->
    <div class="sidebar">
          @each('partials.module', $modules, 'module', 'partials.empty-module')
    </div>
    
    <!-- resources/views/partials/module.blade.php -->
    <div class="sidebar-module">
          <h1>{{ $module->title }}</h1>
    </div>
    
    <!-- resources/views/partials/empty-module.blade.php -->
    <div class="sidebar-module">
          No modules :(
    </div>

    Рассмотрим синтаксис each. Первый параметр — это имя составляющей представления. Второй — массив или коллекция для итерации. Третье — название переменной, под которым каждый элемент (в данном случае каждый элемент в массиве $modules) будет передан представлению. И необязательный четвертый параметр — это представление, показывающее, являются ли массив или коллекция пустыми (или при желании можно передать здесь строку, которая будет использоваться в качестве вашего шаблона).

    Использование стеков


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

    Наиболее распространенная ситуация — когда определенные страницы (а иногда и в более широком смысле определенные разделы сайта) имеют конкретные уникальные файлы CSS и JavaScript, которые им нужно загрузить. Представьте, что у вас есть «глобальный» CSS-файл для всего сайта, CSS-файл раздела вакансий и CSS-файл страницы «Устроиться на работу».

    Стеки Blade созданы именно для такого. В родительском шаблоне определите стек-заполнитель. Затем в каждом дочернем шаблоне вы можете туда «выталкивать» записи с помощью push/@endpush, который добавляет их в конец стека в конечном изображении. Вы также можете использовать @prepend/@endprepend, чтобы добавить их в начало. Пример 4.13 иллюстрирует это.

    Пример 4.13. Использование стеков Blade

    <!-- resources/views/layouts/app.blade.php -->
    <html>
    <head><!-- шапка --></head>
    <body>
           <!-- остальная чать страницы -->
           <script src="/css/global.css"></script>
           <!-- заполнитель, куда будет помещен контент из стека -->
           @stack('scripts')
    </body>
    </html>
    
    <!-- resources/views/jobs.blade.php -->
    @extends('layouts.app')
    
    @push('scripts')
            <!-- выталкиваем что-нибудь в конец стека -->
            <script src="/css/jobs.css"></script>
    @endpush
    
    <!-- resources/views/jobs/apply.blade.php -->
    @extends('jobs')
    
    @prepend('scripts')
           <!-- выталкиваем что-нибудь в начало стека -->
           <script src="/css/jobs--apply.css"></script>
    
    @endprepend

    Это приводит к следующему результату:

    <html>
    <head><!-- шапка --></head>
    <body>
           <!-- остальная часть страницы -->
           <script src="/css/global.css"></script>
           <!-- заполнитель, куда будет помещен контент из стека -->
           <script src="/css/jobs--apply.css"></script>
           <script src="/css/jobs.css"></script>
    </body>
    </html>

    Использование компонентов и слотов


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

    Пример 4.14. Модальное окно — неудачный пример составляющей представления

    <!-- resources/views/partials/modal.blade.php -->
    <div class="modal">
          <div>{{ $content }}</div>
          <div class="close button etc">...</div>
    </div>
    
    <!-- в другом шаблоне -->
    @include('partials.modal', [
           'body' => '<p>The password you have provided is not valid. Here are the rules
           for valid passwords: [...]</p><p><a href="#">...</a></p>'
    ])

    Это слишком много для такой переменной и идеально подходит для компонента.

    Компоненты со слотами — это составляющие представлений, которые разработаны, чтобы включать большие порции («слоты») для получения содержимого из включаемого шаблона. В примере 4.15 показано, как перепроектировать код из примера 4.14 с использованием компонентов и слотов.

    Пример 4.15. Модальное окно как более подходящий компонент со слотами

    <!-- resources/views/partials/modal.blade.php -->
    <div class="modal">
          <div>{{ $slot }}</div>
          <div class="close button etc">...</div>
    </div>
    
    <!-- в другом шаблоне -->
    @component('partials.modal')
            <p>The password you have provided is not valid.
            Here are the rules for valid passwords: [...]</p>
    
            <p><a href="#">...</a></p>
    @endcomponent

    Как вы можете видеть в примере 4.15, директива component позволяет извлекать наш HTML-код из сжатой строки переменной и возвращает в пространство шаблона. Переменная $slot в шаблоне нашего компонента получает любой контент, передаваемый в component.

    Составные слоты


    Метод, который мы использовали в примере 4.15, называется слотом «по умолчанию»; все, что вы передаете между component и @endcomponent, передается переменной $slot. Но вы также можете иметь больше, чем просто слот по умолчанию. Представим модальное окно с заголовком, как в примере 4.16.

    Пример 4.16. Составляющая модального представления с двумя переменными

    <!-- resources/views/partials/modal.blade.php -->
    <div class="modal">
          <div class="modal-header">{{ $title }}</div>
          <div>{{ $slot }}</div>
          <div class="close button etc">...</div>
    </div>

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

    Пример 4.17. Передача более одного слота компоненту

    @component('partials.modal')
            @slot('title')
                   Password validation failure
            @endslot
    
            <p>The password you have provided is not valid.
            Here are the rules for valid passwords: [...]</p>
    
            <p><a href="#">...</a></p>
    @endcomponent

    И если в вашем представлении есть другие переменные, которые бессмысленны в качестве слота, все равно можно передать массив содержимого в качестве второго параметра в component так же, как с include. Взгляните на пример 4.18.

    Пример 4.18. Передача данных в компонент без слотов

    @component('partials.modal', ['class' => 'danger'])
             ...
    @endcomponent

    Именование компонента как директивы


    Есть хитрый трюк, чтобы еще упростить компоненты для вызова: псевдонимы. Просто вызовите Blade::component() на фасаде Blade — наиболее распространенным является метод AppServiceProvider boot() — и передайте ему сначала местоположение компонента, а затем имя желаемой директивы, как показано в примере 4.19.

    Пример 4.19. Псевдонимизация компонента как директивы

    // AppServiceProvider@boot
    Blade::component('partials.modal', 'modal');
    
    <!-- в шаблоне -->
    @modal
             Modal content here
    @endmodalИмпорт


    » Более подробно с книгой можно ознакомиться на сайте издательства
    » Оглавление
    » Отрывок

    Для Хаброжителей скидка 25% по купону — Laravel

    По факту оплаты бумажной версии книги на e-mail высылается электронная книга.

    Комментарии 15

      0
      Извиняемся, что в тексте многократно происходит вызов духов хабра. Если вы знаете как разубедить систему автоматически форматировать @ как упоминание хабравчан, без потери читаемости текста (вне блока листингов) — напишите в личку.
        +2
        А что в книге и данной есть такого, чего нет в документации?
          +2
          русский язык)
            0
            Вы не поверите, но есть и русский перевод документации. Я слежу за тегом laravel на тостере, там масса вопросов из доки, потому что люди зачем-то ищут иные источники информации, а не смотрят в официальные доки.
              0
              Угу, со всеми вытекающими…

              Пока на английский не перевел не понял, о какой подписи метода (method signature) идет речь:
              Подпись метода to() для перенаправлений выглядит следующим образом:
              function to($to = null, $status = 302, $headers = [], $secure = null)


            +1
            Кстати, только что
            получил
            image
            книгу)
              0

              Стоит того что бы купить на бумаге?

                0
                да, удобно подглядывать, когда что-то забыл
              0
              А о какой версии Laravel'a идет речь в книге?
              0
              Жаль что для симфони такой книги нет. Я бы прикупил для версии 4.4-5.*
                0
                Англоязычной литературы, которую имело бы смысл переводить, просто нет. Если кто-то из русских специалистов готов стать автором — пишите мне, обдумаем как это можно сделать.
                  0

                  Можете купить, свежая книга от автора фреймворка — https://symfony.com/doc/5.0/the-fast-track/ru/index.html

                  0
                  В этой книге есть материал посвященный отношениям One-to-Many, Many-to-One and Many-to-Many?

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

                  Самое читаемое