Внедрение экземпляра, привязанного к сервисному контейнеру

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

namespace App\Providers;

use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;

class IcalProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->instance('iCal', function () {
            return new Calendar(config('calendar.name'));
        });
    }
}

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

Итак, я создал контроллер и попытался ввести подсказку для своего экземпляра:

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class CalendarInviteController extends Controller
{
    public function download(iCal $ical, $sessionId)
    {
        dd($ical);
    }
}

Но когда я это сделаю, я получаю сообщение об ошибке:

Класс App \ Http \ Controllers \ iCal не существует

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

public function download(\iCal $ical, $sessionId)

и я получаю сообщение об ошибке:

Класс iCal не существует

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

Я неправильно понимаю документы?

Обновлять

Я также должен упомянуть, что я добавил поставщика услуг в свой config/app.php файл.

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


person Chris Schmitz    schedule 06.10.2015    source источник


Ответы (2)


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

namespace App\Providers;

use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;

class IcalProvider extends ServiceProvider
{
    public function register()
    {
        //register a specific instance of the Calendar class in the container
        $this->app->instance('iCal', new Calendar(config('calendar.name') );
    }
}

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

 $cal = \App::make('iCal');

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

namespace App\Providers;

use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;

class IcalProvider extends ServiceProvider
{
    public function register()
    {
        //the key will be 'Eluceo\iCal\Component\Calendar'
        $this->app->instance( Calendar::class, new Calendar(config('calendar.name') );
    }
}

Теперь в вашем контроллере:

namespace App\Http\Controllers;

//important: specify the Calendar namespace
use Eluceo\iCal\Component\Calendar;

class CalendarInviteController extends Controller
{
    public function download(Calendar $ical, $sessionId)
    {
        dd($ical);
    }
}

Таким образом, Laravel увидит, что вам нужен объект Calendar, и попытается получить его из контейнера службы, ища, существует ли привязка для этого ключа: (потому что это пространство имен класса, который вы указали в контроллере)

Eluceo\iCal\Component\Calendar

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

В предоставленном вами коде вы указали класс iCal, но этот класс нигде не существовал, поэтому Laravel не смог создать экземпляр класса.

person Moppo    schedule 07.10.2015
comment
Это: $this->app->instance( Calendar::class... открыло мне глаза. Я думал, что когда я привязал интерфейс к конкретной реализации, Laravel по существу говорил, что когда вы запрашиваете этот интерфейс, вместо этого возвращайте все, что следует. Опубликованная вами строка кода заставила меня понять, что это действительно так. Когда вы запрашиваете этот полностью квалифицированный класс / интерфейс / что угодно, вместо этого верните то, что следует. Эта разница огромна. Спасибо за указание на это! - person Chris Schmitz; 07.10.2015
comment
да, метод instance используется не для привязки интерфейсов, а для конкретных объектов в поставщике услуг (как если бы это был реестр) - person Moppo; 07.10.2015

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

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

use Eluceo\iCal\Component\Calendar;

class CalendarServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind('App\Services\Calendar', function ($app) {
            return new Calendar(config('calendar.name'));
        });
    }

    public function provides()
    {
        return ['App\Services\Calendar'];
    }
}

Пока вы регистрируете своего поставщика услуг в файле config / app.php, теперь вы можете вводить подсказки для своей календарной зависимости в классах:

use App\Services\Calendar;

class InvitationController extends Controller
{
    protected $calendar;

    public function __construct(Calendar $calendar)
    {
        $this->calendar = $calendar;
    }
}
person Martin Bean    schedule 07.10.2015
comment
Спасибо, Мартин. Обычно я создавал интерфейс, реализовывал его в конкретном классе, а затем привязывал его, но класс, который я пытался связать, был сторонним классом, загруженным через композитор, поэтому я не хотел добавлять implements ... в конкретный класс, который возможно, будет перезаписан во время обновления net composer. Я мог бы создать класс в своей структуре приложения, который расширяет класс поставщика, а затем реализовать на нем интерфейс, но это казалось большим трудом для небольшой вещи, поэтому я хотел использовать экземпляр. Тем не менее, то, что вы описываете, является моим предпочтительным методом. - person Chris Schmitz; 07.10.2015
comment
Я думаю, вы неправильно поняли: вы не редактируете конкретный класс (в каталоге vendor) в любое время. Вы просто привязываете его к выбранному вами интерфейсу в вашем сервис-провайдере. - person Martin Bean; 07.10.2015
comment
Ах, по какой-то причине я подумал, что интерфейс должен быть реализован в конкретном классе, чтобы это работало. Приятно знать, что поставщику услуг в этом нет необходимости. - person Chris Schmitz; 07.10.2015