Может войдёшь?
Черновики Написать статью Профиль

Основы Laravel 5: Сервис-контейнер

перевод Основы Laravel 5 Laracasts

Это перевод видео-урока с Laracasts, серия Laravel 5 Fundamentals, урок №26The Service Container от . Перевод обновлён . Опечатка? Выдели и нажми Ctrl+Enter.

(0:00)
Мы уже прошли 25 видео, но ещё не говорили о сервис-контейнерах. По крайней мере, не в каких-либо подробностях. Так что, нам действительно стоит в данном уроке это исправить. Изначально эта тема может казаться немного запутанной, но как только вы осознаете её, то вы поймёте, что это один из самых больших подарков в Laravel. Давайте начнём с очень простого примера, для разгона, так сказать. Сервис-контейнер, это в действительности то и есть:

(0:30)
думайте об этом как о маленьком контейнере, который вмещает все различные привязки в нашем приложении. Так что, представьте вот что. Скажем, у нас есть:

PHP
Route::get('bar', function(Bar $bar) {
});

и когда это замыкание вызывается, то мне нужен доступ к какому-то объекту PHP$bar. OK, может быть, мы сделаем подсказку формата типа данных на класс PHPBar, и если помните, его ещё не существует. А потом внутри, я просто сделаю:

PHP
dd($bar);

и посмотрим, что мы получим. Хорошо, давайте перейдём к Chrome, откроем laravel5.dev/bar и мы это и ожидали, не так ли?

(1:00)
Класс PHPBar не существует. Но теперь, взгляните на это. Если я создам класс PHPBar на самом верху, и мы вернёмся и обновим – как по волшебству, это сработало. Так что обратите внимание. Это очень важно, и также очень здорово. Здесь мы делаем подсказку формата типа данных на какой-то объект. Мы говорим: «Мы ожидаем что будет передан экземпляр класса PHPBar». Но также заметьте, что нигде мы не делали ничего подобного:

PHP
$bar = new Bar;

(1:30)
и затем мы бы передали замыкание вручную. Мы не делаем ничего из этого, и именно поэтому сервис-контейнер Laravel настолько полезен. Там действительно много умных вещей происходит за кадром. В основном, что происходит, это Laravel использует особенность PHP, которая называется отражением (reflection), и которая позволяет ему как бы подглядеть сюда (PHPBar $bar) и выяснить, что происходит, и что нам нужно. В этом случае, когда Laravel вызывает этот метод, он собирается заглянуть в список параметров и увидит: "О, они ожидают, что будет передан экземпляр класса PHPBar.

(2:00)
Я просто забегу вперёд и сделаю это за них". Но теперь, давайте рассмотрим это ещё внимательнее. И это тоже взорвёт ваш мозг. Допустим PHP$bar ожидает экземпляр класса PHPBaz и мы инициализируем его. И давайте просто сделаем его PHPpublic, чтобы всё было просто:

PHP
public $baz;

ОК, теперь для того, чтобы был создан экземпляр класса PHPBar, мы также должны создать экземпляр класса PHPBaz.

(2:30)
Так что вручную, конечно, вы сказали бы:

PHP
$bar = new Bar(new Baz);

Так? Это был бы ручной способ. Давайте попробуем это и посмотрим, что теперь происходит. Я вернусь и обновлю. И ещё раз мы получаем «Класс PHPBaz не существует». Это значит, если мы сделаем:

PHP
class Baz {}

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

(3:00)
Потому что на самом деле, здесь много чего происходит. Laravel читает это и говорит: «Хорошо, им нужен экземпляр класса PHPBar, так что я сделаю для них новый». Но в процессе выполнения, Laravel снова использует отражение, и видит: «О, чтобы сделать новый объект PHPBar, мне также нужно передать экземпляр PHPBaz, поэтому я также сделаю новый класс и для него». И это будет просто продолжаться рекурсивно. Так что если для PHPBaz нужен экземпляр PHPFoo, то он сделает и его.

(3:30)
И будет просто продолжать копать вниз эту дыру, насколько это только возможно, для того, чтобы создать для вас этот объект PHP$bar. Так что теперь мы можем увидеть, если мы хотим получить доступ к свойству PHPbaz с чем-то вроде этого:

PHP
dd($bar->baz);

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

(4:00)
Но также, если мы не сделаем это вручную, то Laravel будет пытаться сделать это для нас. Вот что я имею в виду, если бы мы сделали это вручную, мы могли бы ссылаться на наш контейнер в приложении, и я мог бы сказать:

PHP
App::bind

или, кстати, есть также функция PHPapp()->bind, так что вы могли бы сделать и так тоже. Но в любом случае, скажем:

PHP
App::bind('Bar', function()
{
    return new 
Bar(new Baz);
});

(4:30)
Теперь, если мы проверим это в Chrome, обновим, и мы снова получаем то же самое, но на этот раз это выглядит немного по-другому. Чтобы дать вам здесь подсказку, я скажу:

PHP
dd('fetching');

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

(5:00)
Вы должны дать ей ключ API. Вы должны дать какую-то другую зависимость. И, опять же, вы не хотите создавать его снова и снова. Так что, вы делаете его один раз, и привязываете его в сервис-контейнер Laravel. Ещё вы можете увидеть, что его называют контейнером IoC (Inversion of Control), что есть то же самое. Таким образом, всякий раз, когда вам это нужно, в классах, которые разрешены из сервис-контейнера вы просто вводите то, что хотите, и Laravel даст вам именно то, что вы указали.

(5:30)
Тем не менее, имейте в виду, как мы и рассмотрели в начале видео, что даже если это специфическое связывание не существовало... Если я вернусь и обновлю, мы снова получим тот же самый результат, поскольку хоть мы и не привязали ничего в контейнер вручную, но Laravel всё ещё может с помощью отражения построить это всё для вас.
И вот ещё некоторые другие вещи, для которых это может быть полезно. Представьте, что вы на самом деле не хотите делать подсказку формата типа данных для конкретного класса.

(6:00)
Может быть, вы создаете пакет или нечто подобное, и вы бы предпочли сослаться на интерфейс. Для того, чтобы мой класс заработал, мне нужна какая-то реализация этого интерфейса. И, кстати, если вы не работали с интерфейсами, у нас есть специальный урок про них, так что просто поищите на Laracasts. Но в любом случае, как мы могли бы с этим справиться? Или, другими словами, скажем, нам нужна какая-то реализация PHPBarInterface.

(6:30)
Я создам это на самом верху:

PHP
interface BarInterface {}

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

PHP
class Bar {}

Хорошо, теперь, что вы думаете произойдёт? Что ж, давайте посмотрим. Обновим, и мы получаем такую странную ошибку. Вы увидите её много раз, когда ещё только начинаете учиться. Target [BarInterface] is not instantiable. Так что же здесь происходит? Прежде чем я дам вам ответ на этот вопрос, представьте себе, если я вернусь сюда, мы создаем привязку и говорим:

(7:00)

PHP
App::bind('BarInterface', function()

помните, это просто ключ, который мы используем для контейнера. И почему бы нам не сделать:

PHP
return new Bar;

и мы должны убедиться, что:

PHP
class Bar implements BarInterface {}

Хорошо, мы переходим обратно в Chrome, обновляем, и снова, это работает, потому что мы вручную добавили привязку в контейнер. Итак, это работает так: когда Laravel вызывает это замыкание, и, кстати, это было бы верно и для конструктора контроллера.

(7:30)
Но в любом случае, Laravel увидит, что мы запрашиваем какой-то объект PHPBarInterface, и в первую очередь он будет смотреть внутрь контейнера, чтобы определить есть ли у нас что-нибудь, что соответствует этому. Думайте об этом как если вы ищете в вашем рюкзаке. Есть ли здесь что-нибудь, с идентификатором или ключом, или именем PHPBarInterface?

(8:00)
Если есть, то, это должно быть именно то, что мы хотим, так что давайте просто вернём всё, что с ним связано. Однако, для тех ситуаций, когда у нас нет такого связывания, и я вернусь и обновлю, опять мы получаем ошибку, что не можем создать экземпляр Target [BarInterface] is not instantiable., поскольку на этот раз это работает иначе. Laravel видит, «Хорошо, нам нужен PHPBarInterface. Есть ли у меня что-нибудь в контейнере или рюкзаке, что похоже на это?». Нет. Там нет ничего, что соответствует PHPBarInterface. Так что дальше Laravel пытается вести себя как хороший код и пробует создать объект за вас.

(8:30)
Но как только он проверит это, то поймёт: "Постойте-ка, это интерфейс, и я не могу создать экземпляр интерфейса. Конечно же, PHP не позволяет это делать. Так что же мне теперь делать? Я понятия не имею, что делать, так что я просто брошу исключение и сообщу пользователю: "Эй, ты дал мне здесь интерфейс, и в контейнере нет привязки, так что я понятия не имею, что здесь делать"". И это именно то, что означает данное сообщение.

(9:00)
Так что, если вы когда-нибудь работаете над своими проектами, и видите это, то вы должны сказать Laravel: «Когда я задаю подсказку типа данных для этого контракта или этого интерфейса, вот этот фактический конкретный класс, который я хочу связать с ним». И вы делаете это так:

PHP
App::bind('BarInterface', function() {});

И мы могли бы сделать здесь замыкание, как делали раньше, и просто вручную построить его. Или, если мы просто передаём здесь строку, вот это сработает:

PHP
App::bind('BarInterface''Bar');

(9:30)
Так что теперь, давайте удостоверимся, что это работает, а потом мы поговорим об этом немного больше. Обновим, и у нас здесь есть именно то, что у нас было раньше. Хорошо, так что же здесь происходит?

PHP
App::bind('BarInterface''Bar');

Это очень простое связывание. Когда тебе нужен PHPBarInterface, я хочу чтобы ты дал мне класс PHPBar. Или, если есть что-то ещё, что вы хотите использовать:

PHP
class SecondBar implements BarInterface {}

Если вы решили, что именно его вы хотите использовать, то просто обновите ваше связывание:

PHP
App::bind('BarInterface''SecondBar');

(10:00)
И теперь, на протяжении всего вашего приложения, где бы вы ни просили объект, который относится к этому контракту, теперь, и опять же по всему приложению, вы будете получать PHPSecondBar, а не PHPBar. И вы только должны были поправить здесь одну строку, чтобы это отразилось везде.
Итак, почему бы нам не охватить ещё пару вещей, и на этом и остановимся.
В этом случае, поскольку мы задали подсказку формата типа данных, Laravel автоматически создал для нас объект, как по волшебству.

(10:30)
Мы даже не видели, как это происходило. Но также могут возникнуть ситуации, когда вам придётся строить вещи самостоятельно. Итак, как быть в этих ситуациях? Давайте скажем:

PHP
$bar App::make

Таким образом, у нас есть PHPbind() для связывания чего-то с контейнером, а затем PHPApp::make() для извлечения из контейнера. Это терминология, которую мы будем использовать. Вы связываете с контейнером, а затем, когда вы хотите упаковать, вы извлекаете из контейнера.

(11:00)
Так что теперь, если я скажу:

PHP
$bar App::make('Bar');

или

PHP
$bar App::make('BarInterface');

и давайте сделаем PHPdd($bar);, то мы получим тот же результат. Обновим и готово.
Теперь не стоит забывать, как я уже говорил ранее, если предпочитаете, вы могли бы использовать и этот синтаксис:

PHP
$bar app()->make('BarInterface');

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

PHP
$bar app()['BarInterface'];

Вы могли бы ссылаться на него как на массив. Контейнер реализует доступ к массиву, то есть интерфейс, который определяет, как вам получить доступ к чему-то как к массиву.

(11:30)
Так что это тоже будет работать. Вернёмся, обновим и готово. Или, ещё один момент напоследок. Если бы мы передали его как параметр, это тоже привело бы к тому же конечному результату:

PHP
$bar app('BarInterface');

Вернёмся, обновим и оно всё ещё работает. Теперь очень быстро, почему именно это так? Что ж, давайте заглянем в функцию. Обратите внимание, если мы передаём здесь параметр (PHP$make null), Laravel предположит:

(12:00)
«О, вы пытаетесь извлечь что-то, так что я вызову для вас PHPapp()->make($make), но если вы ничего там не передаёте, то мы просто выдадим вам текущий контейнер».
Хорошо, я переключусь обратно, и, чтобы завершить, до сих пор мы работали с контроллерами, но теперь я показываю это с замыканиями маршрута. Помните, что вы можете использовать оба. Так почему бы нам не сделать это, вот действительно распространённый пример. Мы говорили в прошлом уроке об идее хранилища (repository).

(12:30)
И по правде говоря, я не хочу, чтобы вы слишком много об этом думали, если вы только начинаете изучать Laravel. Но мы сделаем небольшое исключение только для этого видео, чтобы увидеть рабочий процесс. Хорошо, мы создадим здесь новый контроллер. Скажем:

PHP
Route::get('foo''FooController@foo');

Хорошо, давайте создадим вот что:

shphp artisan make:controller FooController --plain

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

(13:00)
Хорошо, теперь у нас есть PHPFooController. Я создам метод PHPfoo(). Убедимся, что это работает:

PHP
return 'foo';

Вернёмся в Chrome, зайдём на laravel5.dev/foo и готово. Теперь, как выясняется, этому методу нужен доступ к какому-то классу-хранилищу. Итак, почему бы нам не пойти дальше и не создать его. Мы могли бы поместить его в каталог app. Так что я создам app/Repositories и затем внутри класс, который назову PHPFooRepository.

(13:30)
Теперь здесь, давайте просто сделаем метод PHPget(), где мы возвращаем массив элементов или результат запроса к базе данных или что-то, для чего вы используете Eloquent, нечто такое. Этого будет достаточно:

PHP
class FooRepository {
    public function 
get()
    {
        return [
'array''of''items'];
    }
}

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

(14:00)
Так что мы действительно не хотели бы делать что-то вроде:

PHP
$repository = new \App\Repositories\FooRepository();

Да, это будет работать, и на самом деле, если мы хотим это увидеть, то давайте:

PHP
return $repository->get();

вернёмся в Chrome, обновим и вот он наш массив. Но мы не хотим на самом деле так делать. Обычно это считается плохой практикой. Это делает гораздо более трудной проверку такого кода.

(14:30)
И это делает более трудным анализ необходимых зависимостей для этого класса. Так что как правило, будьте осторожны со словом PHPnew, если только вы не имеете дело с такими понятиями как сущность-объект или объект значений, или вещами такого рода. Итак, я покажу вам два различных способа как справиться с этим. Одним из них является внедрение конструктора, а другим будет внедрение метода. Позвольте я вам покажу. Для начала, в пределах нашего конструктора, как мы и делали с замыканием маршрута, я запрошу экземпляр класса PHPFooRepository.

(15:00)
И мы скажем:

PHP
$this->repository $repository;

(инициализируем эти поля). И просто примите к сведению, что мы здесь сделали ссылку на полный путь:

PHP
use App\Repositories\FooRepository;

ОК, так что теперь я мог бы сказать:

PHP
return $this->repository->get();

и мы должны получить тот же результат, и точно. Но теперь здорово то, что вы точно знаете, что именно здесь происходит.

(15:30)
Laravel смотрит в свой сервис-контейнер, чтобы дать вам то, что вам требуется. И в этом случае, мы ничего не связали, но он увидел: «ОК, они хотят PHPFooRepository, похоже я сам могу создать экземпляр этого объекта, так что я сделаю это для пользователя и передам его сюда». Так что это очень здорово, не так ли? Но также вы можете обнаружить, что иногда у вас будет такой метод контроллера, где вам нужна какая-то зависимость. Но на самом деле, по правде говоря, этот метод является единственным местом, где вам нужна зависимость.

(16:00)
И в тех ситуациях, это своего рода досадно, что вам придется внедрять его в контроллер, когда только один метод будет использовать его. В Laravel 5 у нас есть решение для этого. Вы можете использовать то, что мы называем внедрением метода, и он работает в основном точно таким же образом. Вы делаете подсказку формата типа данных на то, что вам нужно, и затем, когда Laravel вызывает его, он проделает точно такой же процесс, где он пытается для вас создать объект. Так что теперь я мог бы изменить это так:

PHP
return $repository->get();

и мы опять же получим ту же самую вещь.

(16:30)
Так что если вы когда-либо пытаетесь решить, «Мне здесь стоит использовать внедрение метода или внедрение конструктора?». Основное правило для этого ещё раз, если вам нужно вызывать его только один раз, то просто используйте внедрение метода. Но если у вас есть два или три метода, которым нужен PHPFooRepository, то в тех случаях, больше смысла делать внедрение через конструктор. Теперь, конечно же, нужно иметь в виду, что это не означает, что каждый отдельный метод во всём приложении может использовать внедрение метода.

(17:00)
Это просто конкретные компоненты, которые предлагают это, в данном случае, метод контроллера. Laravel вызывает этот метод для вас за кадром, так что мы можем использовать это. И то же самое верно и для таких вещей, как обработчики команд, или критические процессы, или однообразные сценарии и тому подобное. Так что имейте это в виду. ОК, знаете, что я думаю? Это всё, что мы здесь осветим. Некоторые из этих моментов возможно были запутанными, особенно если вы новичок для всех этих концепций внедрения зависимостей и сервис-контейнеров, и всего, что с этим связано.

(17:30)
Так что я рекомендую, если это всё вам ещё не до конца понятно, то посмотрите это видео снова. Мы много говорим об этом на Laracasts. Так что, может быть посмотрите другие видео и постарайтесь освоиться с этим, потому что это своего рода фундамент, основа разработки приложений в Laravel.

Как вы считаете, полезен ли этот материал? Да Нет

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.