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

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)
{
    // ...
}
Richsage
источник
есть ли способ обработать, если параметр не существует? вместо исключения 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'
оттенки3002
источник
Предоставленный код не использует DI должным образом - внедрение всего контейнера считается плохим стилем, так как вы скрываете настоящие зависимости
Нико Хаасе
Я думаю, что вы ошибаетесь в понятиях, в примере я показываю только общий случай. Если сомневаетесь, обратитесь к официальной документации Symfony перед тем, как голосовать. symfony.com/doc/current/components/dependency_injection.html
shades3002
Вы можете объяснить это дальше? В связанной документации четко указано, что внедрение контейнера не является хорошей идеей, и не показано никаких примеров, использующих этот тип инъекции - очевидно, что вы не вводите зависимости, когда вводите весь контейнер
Нико Хаасе,