Как получить доступ к параметрам приложения из службы?


81

Из своих контроллеров я получаю доступ к параметрам приложения (тем, что есть /app/config) с помощью

$this->container->getParameter('my_param')

Но я не знаю, как получить к нему доступ из службы (я полагаю, что мой класс обслуживания не должен расширяться Symfony\Bundle\FrameworkBundle\Controller\Controller).

Должен ли я отобразить необходимые параметры в моей регистрации службы следующим образом:

#src/Me/MyBundle/Service/my_service/service.yml
parameters:
    my_param1: %my_param1%
    my_param2: %my_param2%
    my_param3: %my_param3%

или что-то подобное? Как мне получить доступ к параметрам моего приложения из службы?


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



Мой вопрос на самом деле отвечает на этот (параметры от контроллера), я говорю о доступе из службы здесь
— Пьер де ЛЕСПИНЭ

Я не уверен, что понимаю тебя. Вы согласны с дубликатом? В настоящее время контроллеры - это службы в Symfony.
— Tomáš Votruba

Я не согласен с дубликатом. Другой вопрос конкретно касается контроллеров, с которыми легко получить параметры $this->getParameter().
— Pierre de LESPINAY 05

Это правда, я согласен. И это все еще возможно. Также существует тенденция отходить от инъекции контейнера куда угодно и переходить к инъекции конструктора. Благодаря автообнаружению службы PSR-4 и привязке параметров: symfony.com/blog/new-in-symfony-3-4-local-service-binding , он чист и намного короче для работы.
— Tomáš Votruba

Ответы:


123

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

services:
    my_service:
        class:  My\Bundle\Service\MyService
        arguments: [%my_param1%, %my_param2%]

где %my_param1%etc соответствует параметру с именем my_param1. Тогда ваш конструктор класса обслуживания может быть:

public function __construct($myParam1, $myParam2)
{
    // ...
}

есть ли способ обработать, если параметр не существует? вместо исключения Symfony IOC.
— Мохаммед Яссин ЧАБЛИ

и откуда взялось значение my_param1?
— Sliq 05

@Sliq, вы определяете его в parameters.yml
— График

35

Чистый путь 2018

Начиная с 2018 и Symfony 3.4, существует гораздо более чистый способ - простой в настройке и использовании.

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

Как настроить за 2 шага?

1. config.yml

# config.yml
parameters:
    api_pass: 'secret_password'
    api_user: 'my_name'

services:
    _defaults:
        autowire: true
        bind:
            $apiPass: '%api_pass%'
            $apiUser: '%api_user%'

    App\:
        resource: ..

2. Любые Controller

<?php declare(strict_types=1);

final class ApiController extends SymfonyController
{
    /**
     * @var string 
     */
    private $apiPass;

    /**
     * @var string
     */
    private $apiUser;

    public function __construct(string $apiPass, string $apiUser)
    {
        $this->apiPass = $apiPass;
        $this->apiUser = $apiUser;
    }

    public function registerAction(): void
    {
        var_dump($this->apiPass); // "secret_password"
        var_dump($this->apiUser); // "my_name"
    }
}

Готово к мгновенному обновлению!

Если вы используете более старый подход, вы можете автоматизировать его с помощью Rector .

Читать больше

Это называется подходом с внедрением конструктора поверх локатора сервисов .

Чтобы узнать больше об этом, прочтите мой пост Как получить параметр в контроллере Symfony в чистом виде .

(Он протестирован, и я постоянно обновляю его до новой основной версии Symfony (5, 6 ...)).


1
Я бы взял что-то еще, кроме класса контроллера, в качестве примера кода, поскольку OP хочет вводить параметры в любой сервис, а автоматическое подключение включено по умолчанию в контроллерах SF3
— alpadev

Спасибо за ваш комментарий. Приведенная выше конфигурация работает для любой службы, контроллера, репозитория или собственной службы. Нет никакой разницы.
— Tomáš Votruba

18

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

Для этого:

Внесите следующие изменения в свой класс обслуживания

use Symfony\Component\DependencyInjection\ContainerInterface; // <- Add this

class MyServiceClass
{
    private $container; // <- Add this
    public function __construct(ContainerInterface $container) // <- Add this
    {
        $this->container = $container;
    }
    public function doSomething()
    {
        $this->container->getParameter('param_name_1'); // <- Access your param
    }
}

Добавьте @service_container в качестве «аргументов» в свой services.yml

services:
  my_service_id:
    class: ...\MyServiceClass
    arguments: ["@service_container"]  // <- Add this

1
Именно то, что я искал, поэтому мне нравится инъекция зависимостей :)
— klimpond

44
-1. Передача контейнера полностью противоречит цели внедрения зависимостей. Вашему классу следует предоставить только то, что ему действительно нужно для работы, а не весь контейнер.
— richsage

@richsage, есть ли альтернатива для достижения аналогичных результатов - чтобы объявление службы не обновлялось для каждого параметра? Это также выглядит немного аккуратнее, чем ввод параметров один за другим.
— Batandwa

1
Передача всего контейнера в сервис - действительно плохая идея. Как говорит @richsage, это не соответствует цели внедрения зависимостей. Если вы не хотите использовать инъекцию зависимостей, тогда не используйте Symfony2 :)
— терсакян

2
@tersakyan, а как насчет контроллеров тогда? по умолчанию все контроллеры имеют доступ к контроллеру. Тогда не следует ли нам использовать и контроллеры? :)
— Alex Zheka

9

Начиная с symfony 4.1, есть новый очень чистый способ добиться этого.

<?php
// src/Service/MessageGeneratorService.php

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

class MessageGeneratorService
{
 private $params;
 public function __construct(ParameterBagInterface $params)
 {
      $this->params = $params;
 }
 public function someMethod()
 {
     $parameterValue = $this->params->get('parameter_name');
...
 }
}

источник: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service .


6

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

Итак, расширяя ответ @richsage:

parameters.yml

parameters:
    array_param_name:
        param_name_1:   "value"
        param_name_2:   "value"

services.yml

services:
    my_service:
        class:  My\Bundle\Service\MyService
        arguments: [%array_param_name%]

Затем доступ внутри класса

public function __construct($params)
{
    $this->param1 = array_key_exists('param_name_1',$params)
        ? $params['param_name_1'] : null;
    // ...
}

На момент написания этого комментария, к сожалению, вложение параметров в Symfony невозможно, см. Документы: symfony.com/doc/current/service_container/…
— Tomáš

5

С Symfony 4.1 решение довольно простое.

Вот отрывок из исходного сообщения:

// src/Service/MessageGenerator.php
// ...

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

class MessageGenerator
{
    private $params;

    public function __construct(ParameterBagInterface $params)
    {
        $this->params = $params;
    }

    public function someMethod()
    {
        $parameterValue = $this->params->get('parameter_name');
        // ...
    }
}

Ссылка на исходный пост: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service


0

@richsage правильный (для Symfony 3.?), но он не работал для моего Symfony 4.x. Итак, это для Symfony 4.

в файле services.yaml

parameters:
    param1: 'hello'

Services:
    App\Service\routineCheck:
            arguments:
                $toBechecked: '%param1%'  # argument must match in class constructor

в вашем файле класса обслуживания systemCheck.php создайте конструктор следующим образом

private $toBechecked;

public function __construct($toBechecked)
{
    $this->toBechecked = $toBechecked;
}

public function echoSomething()
{
    echo $this->toBechecked;
}

Готово.


Вы можете объяснить это дальше? Что именно не сработало с другим решением - приводятся ли сообщения об ошибках?
— Нико Хаасе

Он использовал ParameterBagInterface $ params в своем конструкторе, но чтобы полностью использовать конфигурацию параметров в services.yaml, я использовал внедрение зависимостей.
— Dung

Вы можете объяснить это дальше? Ответ richsage не содержит этого ParameterBagInterface, а содержит список вводимых параметров, как и ваш код
— Нико Хаасе

Мой ответ был опубликован в 2012 году, когда экосистемой была только Symfony 2. Я больше не использую Symfony, поэтому не обновлялся для последующих версий.
— richsage

-1

Symfony 3.4 здесь.

После некоторых исследований я не думаю, что передача параметров классу / сервису через его конструктор всегда является хорошей идеей. Представьте, что вам нужно передать контроллеру / службе еще несколько параметров, чем 2 или 3. Что тогда? Было бы смешно передавать, скажем, до 10 параметров.

Вместо этого используйте ParameterBagкласс как зависимость при объявлении службы в yml, а затем используйте столько параметров, сколько захотите.

Конкретный пример, допустим, у вас есть почтовая служба, такая как PHPMailer, и вы хотите, чтобы параметры подключения PHPMailer были в paramters.ymlфайле:

#parameters.yml
parameters:
    mail_admin: abc@abc.abc
    mail_host: mail.abc.com
    mail_username: noreply@abc.com
    mail_password: pass
    mail_from: contact@abc.com
    mail_from_name: contact@abc.com
    mail_smtp_secure: 'ssl'
    mail_port: 465

#services.yml
services:
    app.php_mailer:
        class: AppBundle\Services\PHPMailerService
        arguments: ['@assetic.parameter_bag'] #here one could have other services to be injected
        public: true

# AppBundle\Services\PHPMailerService.php
...
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
...
class PHPMailerService
{
    private $parameterBag;
    private $mailAdmin;
    private $mailHost;
    private $mailUsername;
    private $mailPassword;
    private $mailFrom;
    private $mailFromName;
    private $mailSMTPSecure;
    private $mailPort;
}
public function __construct(ParameterBag $parameterBag)
{
    $this->parameterBag = $parameterBag;

    $this->mailAdmin      = $this->parameterBag->get('mail_admin');
    $this->mailHost       = $this->parameterBag->get('mail_host');
    $this->mailUsername   = $this->parameterBag->get('mail_username');
    $this->mailPassword   = $this->parameterBag->get('mail_password');
    $this->mailFrom       = $this->parameterBag->get('mail_from');
    $this->mailFromName   = $this->parameterBag->get('mail_from_name');
    $this->mailSMTPSecure = $this->parameterBag->get('mail_smtp_secure');
    $this->mailPort       = $this->parameterBag->get('mail_port');
}
public function sendEmail()
{
    //...
}

Я думаю, это лучший способ.


-1

В symfony 4 мы можем получить доступ к параметрам посредством внедрения зависимостей:

Сервисы:

   use Symfony\Component\DependencyInjection\ContainerInterface as Container;

   MyServices {

         protected $container;
         protected $path;

         public function __construct(Container $container)
         {
             $this->container = $container;
             $this->path = $this->container->getParameter('upload_directory');
         }
    }

parameters.yml:

parameters:
     upload_directory: '%kernel.project_dir%/public/uploads'

Предоставленный код не использует DI должным образом - внедрение всего контейнера считается плохим стилем, так как вы скрываете настоящие зависимости
— Нико Хаасе

Я думаю, что вы ошибаетесь в понятиях, в примере я показываю только общий случай. Если сомневаетесь, обратитесь к официальной документации Symfony перед тем, как голосовать. symfony.com/doc/current/components/dependency_injection.html
— shades3002

Вы можете объяснить это дальше? В связанной документации четко указано, что внедрение контейнера не является хорошей идеей, и не показано никаких примеров, использующих этот тип инъекции - очевидно, что вы не вводите зависимости, когда вводите весь контейнер
— Нико Хаасе,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.