Jak uzyskać dostęp do parametrów aplikacji z usługi?

81

Z moich kontrolerów mam dostęp do parametrów aplikacji (tych w /app/config) z

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

Ale nie wiem, jak uzyskać do niego dostęp z usługi (wyobrażam sobie, że moja klasa usług nie powinna się rozszerzać Symfony\Bundle\FrameworkBundle\Controller\Controller).

Czy powinienem zmapować potrzebne parametry do rejestracji mojej usługi w następujący sposób:

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

czy coś podobnego? Jak mogę uzyskać dostęp do parametrów mojej aplikacji z usługi?


To pytanie wydaje się takie samo, ale moje faktycznie na nie odpowiada (parametry ze sterownika), mówię o dostępie z usługi.

Pierre de LESPINAY
źródło
Moje pytanie faktycznie odpowiada na to (parametry ze sterownika), mówię tutaj o dostępie z usługi
Pierre de LESPINAY
Nie jestem pewien, czy cię rozumiem. Czy zgadzasz się z duplikatem? Obecnie kontrolery są usługami w Symfony.
Tomáš Votruba
Nie zgadzam się z duplikatem. Drugie pytanie dotyczy konkretnie kontrolerów, za pomocą których łatwo uzyskuje się parametry $this->getParameter().
Pierre de LESPINAY
To prawda, zgadzam się. I nadal jest to możliwe. Istnieje również tendencja do odchodzenia od wstrzykiwania pojemnika w dowolne miejsce i przechodzenia do wstrzykiwania konstruktora. Dzięki automatycznemu wykrywaniu usługi PSR-4 i wiązaniu parametrów: symfony.com/blog/new-in-symfony-3-4-local-service-binding jest czysty i znacznie krótszy w użyciu.
Tomáš Votruba

Odpowiedzi:

123

Parametry można przekazywać do usługi w taki sam sposób, jak wstrzykujesz inne usługi, określając je w definicji usługi. Na przykład w YAML:

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

gdzie %my_param1%etc odpowiada parametrowi o nazwie my_param1. Wtedy twój konstruktor klasy usług mógłby wyglądać następująco:

public function __construct($myParam1, $myParam2)
{
    // ...
}
bogata
źródło
czy istnieje sposób postępowania w przypadku, gdy parametr nie istnieje? pochodzi z wyjątku IOC w symfony.
Mohammed Yassine CHABLI,
i skąd my_param1bierze się wartość ?
Sliq
@Sliq, definiujesz to w parameters.yml
Graph
35

Czysta droga 2018

Od 2018 roku i Symfony 3.4 istnieje znacznie czystszy sposób - łatwy w konfiguracji i obsłudze.

Zamiast używać kontenera i anty-wzorca lokalizatora usługi / parametrów, możesz przekazać parametry do klasy za pośrednictwem jej konstruktora . Nie martw się, nie jest to czasochłonna praca, ale raczej konfiguracja raz i zapomnij .

Jak to ustawić w 2 krokach?

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. Dowolne 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"
    }
}

Gotowe do natychmiastowej aktualizacji!

Jeśli korzystasz ze starszego podejścia, możesz je zautomatyzować za pomocą Rectora .

Czytaj więcej

Nazywa się to wstrzyknięciem konstruktora do lokalizatora usług podejściem .

Aby dowiedzieć się więcej na ten temat, zajrzyj do mojego postu Jak uzyskać parametry w kontrolerze Symfony w czysty sposób .

(Jest przetestowany i aktualizuję go pod kątem nowej głównej wersji Symfony (5, 6 ...)).

Tomáš Votruba
źródło
1
Wziąłbym
Dzięki za komentarz. Powyższa konfiguracja działa dla dowolnej usługi, kontrolera, repozytorium lub własnej usługi. Nie ma różnicy.
Tomáš Votruba
18

Zamiast mapować potrzebne parametry jeden po drugim, dlaczego nie pozwolić usłudze na bezpośredni dostęp do kontenera? W ten sposób nie musisz aktualizować mapowania, jeśli dodano nowe parametry (które dotyczą Twojej usługi).

Aby to zrobić:

Wprowadź następujące zmiany w klasie usługi

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
    }
}

Dodaj @service_container jako „argumenty” w swoim services.yml

services:
  my_service_id:
    class: ...\MyServiceClass
    arguments: ["@service_container"]  // <- Add this
Ham L.
źródło
1
Dokładnie to, czego szukałem, dlatego lubię wtrysk zależności :)
klimpond
44
-1. Przekazywanie pojemnika w całości mija się z celem wstrzyknięcia zależności. Twoja klasa powinna mieć tylko to, czego faktycznie potrzebuje do działania, a nie cały kontener.
Richsage
@richsage, czy istnieje alternatywa dla osiągnięcia podobnych wyników - więc deklaracja usługi nie jest aktualizowana dla każdego parametru? To również wygląda trochę lepiej niż wprowadzanie parametrów jeden po drugim.
Batandwa
1
Przekazanie całego kontenera do serwisu to naprawdę zły pomysł. Jak mówi @richsage, nie pasuje do celu wstrzykiwania zależności. Jeśli nie chcesz używać wstrzykiwania zależności, nie używaj Symfony2 :)
tersakyan
2
@tersakyan, ale co z kontrolerami? domyślnie wszystkie kontrolery mają dostęp do kontrolera. Czy zatem nie powinniśmy również używać kontrolerów? :)
Alex Zheka
9

Od czasów symfony 4.1 jest nowy, bardzo czysty sposób na osiągnięcie tego celu

<?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');
...
 }
}

źródło: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service .

Ousmane
źródło
6

Aby rozwiązać niektóre z wymienionych problemów, definiuję parametr tablicy, a następnie wstrzykuję go. Dodanie nowego parametru później wymaga jedynie dodania do tablicy parametrów bez zmiany argumentów lub konstrukcji service_container.

A więc rozszerzenie @richsage answer:

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%]

Następnie wejdź do klasy

public function __construct($params)
{
    $this->param1 = array_key_exists('param_name_1',$params)
        ? $params['param_name_1'] : null;
    // ...
}
Dave
źródło
W chwili pisania tego komentarza, niestety zagnieżdżanie parametrów nie jest możliwe w Symfony, patrz docs: symfony.com/doc/current/service_container/ ...
Tomáš Votruba
5

Dzięki Symfony 4.1 rozwiązanie jest dość proste.

Oto fragment oryginalnego posta:

// 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');
        // ...
    }
}

Link do oryginalnego posta: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service

Carol-Theodor Pelu
źródło
0

@richsage jest poprawne (dla Symfony 3.?), ale nie działało z moim Symfony 4.x. A więc tutaj jest Symfony 4.

w pliku services.yaml

parameters:
    param1: 'hello'

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

w pliku klasy usług routineCheck.php wykonaj taki konstruktor

private $toBechecked;

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

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

Gotowe.

Łajno
źródło
Czy możesz to dalej wyjaśnić? Co dokładnie nie zadziałało z innym rozwiązaniem - czy są wyświetlane jakieś komunikaty o błędach?
Nico Haase
Użył ParameterBagInterface $ params w swoim konstruktorze, ale aby w pełni wykorzystać konfigurację parametrów w services.yaml, użyłem iniekcji zależności.
Dung
Czy możesz to dalej wyjaśnić? Odpowiedź Richsage nie zawiera tego ParameterBagInterface, ale listę parametrów do wstrzyknięcia, tak jak twój kod
Nico Haase,
Moja odpowiedź została opublikowana w 2012 roku, kiedy ekosystemem był tylko Symfony 2. Nie używam już Symfony, więc nie aktualizowałem ich dla kolejnych wersji.
richsage
-1

Symfony 3.4 tutaj.

Po kilku badaniach uważam, że przekazywanie parametrów do klasy / usługi za pośrednictwem jej konstruktora nie jest zawsze dobrym pomysłem. Wyobraź sobie, że musisz przekazać do kontrolera / usługi więcej parametrów niż 2 lub 3. Co wtedy? Byłoby śmieszne, gdybyśmy przekazali, powiedzmy, do 10 parametrów.

Zamiast tego użyj ParameterBagklasy jako zależności podczas deklarowania usługi w yml, a następnie użyj dowolnej liczby parametrów.

Konkretny przykład, powiedzmy, że masz usługę pocztową, taką jak PHPMailer, i chcesz mieć parametry połączenia PHPMailer w paramters.ymlpliku:

#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()
{
    //...
}

Myślę, że to lepszy sposób.

Dan Costinel
źródło
-1

W symfony 4 możemy uzyskać dostęp do parametrów poprzez wstrzyknięcie zależności:

Usługi:

   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'
odcienie 3002
źródło
Dostarczony kod nie używa poprawnie DI - wstrzyknięcie całego kontenera jest uważane za zły styl, ponieważ ukrywasz prawdziwe zależności
Nico Haase,
Myślę, że mylisz koncepcje, w przykładzie pokazuję tylko ogólny przypadek. W razie wątpliwości zapoznaj się z oficjalną dokumentacją symfony przed oddaniem głosu. symfony.com/doc/current/components/dependency_injection.html
shades3002
Czy możesz to dalej wyjaśnić? Połączona dokumentacja jasno stwierdza, że ​​wstrzyknięcie pojemnika nie jest dobrym pomysłem i nie pokazuje żadnego przykładu, który używa tego typu wtrysku - tak jak wyraźnie, nie wstrzykujesz zależności, kiedy wstrzykujesz cały pojemnik
Nico Haase