Wie kann ich von einem Dienst aus auf Anwendungsparameter zugreifen?

81

Über meine Controller greife ich mit auf die Anwendungsparameter (die in /app/config) zu

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

Ich weiß jedoch nicht, wie ich von einem Dienst aus darauf zugreifen soll (ich kann mir vorstellen, dass meine Dienstklasse nicht erweitert werden soll Symfony\Bundle\FrameworkBundle\Controller\Controller).

Sollte ich die erforderlichen Parameter wie folgt in meine Serviceregistrierung einordnen:

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

oder etwas ähnliches? Wie soll ich von einem Dienst aus auf meine Anwendungsparameter zugreifen?


Diese Frage scheint dieselbe zu sein, aber meine beantwortet sie tatsächlich (Parameter von einem Controller). Ich spreche über den Zugriff von einem Dienst aus.

Pierre de LESPINAY
quelle
Meine Frage beantwortet tatsächlich diese Frage (Parameter von einem Controller). Ich spreche über den Zugriff von einem Dienst hier
Pierre de LESPINAY
Ich bin mir nicht sicher, ob ich dich verstehe. Stimmen Sie dem Duplikat zu? Controller sind heutzutage Dienste in Symfony.
Tomáš Votruba
Ich bin mit dem Duplikat nicht einverstanden. Die andere Frage betrifft speziell Controller, mit denen leicht Parameter abgerufen werden können $this->getParameter().
Pierre de LESPINAY
Das stimmt, ich stimme zu. Und es ist immer noch möglich. Es gibt auch den Trend, sich vom irgendwo injizierten Behälter zu entfernen und zur Konstruktorinjektion überzugehen. Dank der automatischen Erkennung des PSR-4-Dienstes und der Bindung der Parameter: symfony.com/blog/new-in-symfony-3-4-local-service-binding ist es sauber und viel kürzer zu bearbeiten.
Tomáš Votruba

Antworten:

122

Sie können Parameter auf dieselbe Weise an Ihren Dienst übergeben, wie Sie andere Dienste einfügen, indem Sie sie in Ihrer Dienstdefinition angeben. Zum Beispiel in YAML:

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

wobei das %my_param1%etc einem Parameter mit dem Namen entspricht my_param1. Dann könnte Ihr Serviceklassenkonstruktor sein:

public function __construct($myParam1, $myParam2)
{
    // ...
}
Reichtum
quelle
Gibt es eine Möglichkeit zu behandeln, falls der Parameter nicht existiert? anstelle der Symfony-Ausnahme IOC.
Mohammed Yassine CHABLI
und woher kommt der Wert my_param1von?
Sliq
34

Der saubere Weg 2018

Seit 2017 und Symfony 3.4 gibt es viel sauberere Wege - einfach einzurichten und zu verwenden.

Anstatt das Anti-Pattern für Container und Service / Parameter Locator zu verwenden, können Sie Parameter über den Konstruktor an die Klasse übergeben . Keine Sorge, es ist keine zeitaufwändige Arbeit, sondern ein einmaliges Einrichten und Vergessen des Ansatzes.

Wie richte ich es in 2 Schritten ein?

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

Sofortiges Upgrade bereit!

Wenn Sie einen älteren Ansatz verwenden, können Sie ihn mit Rector automatisieren .

Weiterlesen

Dies wird als Konstruktorinjektion über den Dienstlokalisierungsansatz bezeichnet .

Weitere Informationen hierzu finden Sie in meinem Beitrag So erhalten Sie Parameter in Symfony Controller auf saubere Weise .

(Es wurde getestet und ich halte es für die neue Symfony-Hauptversion (5, 6 ...) auf dem neuesten Stand.)

Tomáš Votruba
quelle
1
Ich hätte etwas anderes als eine Controller-Klasse als Codebeispiel genommen, da OP Parameter in jeden Dienst einfügen möchte und das automatische Verdrahten in SF3-Controllern standardmäßig aktiviert ist
alpadev
Vielen Dank für Ihren Kommentar. Die obige Konfiguration funktioniert für jeden Dienst, Controller, Repository oder eigenen Dienst. Es gibt keinen Unterschied.
Tomáš Votruba
18

Warum sollten Sie Ihrem Dienst nicht erlauben, direkt auf den Container zuzugreifen, anstatt die benötigten Parameter einzeln zuzuordnen? Auf diese Weise müssen Sie Ihr Mapping nicht aktualisieren, wenn neue Parameter hinzugefügt werden (die sich auf Ihren Service beziehen).

Um dies zu tun:

Nehmen Sie die folgenden Änderungen an Ihrer Serviceklasse vor

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

Fügen Sie @service_container als "Argumente" in Ihre services.yml ein

services:
  my_service_id:
    class: ...\MyServiceClass
    arguments: ["@service_container"]  // <- Add this
Ham L.
quelle
1
Genau das, wonach ich gesucht habe, deshalb mag ich Abhängigkeitsinjektion :)
klimpond
43
-1. Wenn der Container vollständig herumgereicht wird, wird der Zweck der Abhängigkeitsinjektion zunichte gemacht. Ihrer Klasse sollte nur das gegeben werden, was sie tatsächlich zum Betrieb benötigt, nicht der gesamte Container.
Richsage
@richsage, gibt es eine Alternative, um ähnliche Ergebnisse zu erzielen - daher wird die Dienstdeklaration nicht für jeden Parameter aktualisiert? Dies sieht auch etwas ordentlicher aus, als Parameter einzeln zu injizieren.
Batandwa
1
Es ist eine wirklich schlechte Idee, den gesamten Container an einen Dienst zu übergeben. Wie @richsage sagt, passt es nicht zum Zweck der Abhängigkeitsinjektion. Wenn Sie keine Abhängigkeitsinjektion verwenden möchten, verwenden Sie Symfony2 nicht :)
Tersakyan
2
@tersakyan, aber was ist dann mit Controllern? Standardmäßig haben alle Controller Zugriff auf den Controller. Sollten wir dann nicht auch Controller verwenden? :)
Alex Zheka
9

Seit Symfony 4.1 gibt es einen sehr sauberen neuen Weg, dies zu erreichen

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

Quelle: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service .

Ousmane
quelle
6

Als Lösung für einige der genannten Probleme definiere ich einen Array-Parameter und füge ihn dann ein. Das spätere Hinzufügen eines neuen Parameters erfordert lediglich das Hinzufügen zum Parameterarray, ohne dass die Argumente oder das Konstrukt von service_container geändert werden müssen.

Also auf @richsage Antwort erweitern:

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

Dann Zugriff innerhalb der Klasse

public function __construct($params)
{
    $this->param1 = array_key_exists('param_name_1',$params)
        ? $params['param_name_1'] : null;
    // ...
}
Dave
quelle
Zum Zeitpunkt des Schreibens dieses Kommentars ist das Verschachteln von Parametern in Symfony leider nicht möglich. Siehe Dokumente: symfony.com/doc/current/service_container/…
Tomáš Votruba
5

Mit Symfony 4.1 die Lösung recht einfach.

Hier ist ein Ausschnitt aus dem ursprünglichen Beitrag:

// 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 zum Originalbeitrag: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service

Carol-Theodor Pelu
quelle
0

@richsage ist korrekt (für Symfony 3.?), hat aber für mein Symfony 4.x nicht funktioniert. Also hier ist für Symfony 4.

in der Datei services.yaml

parameters:
    param1: 'hello'

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

Führen Sie in Ihrer Serviceklasse die Datei RoutineCheck.php so aus

private $toBechecked;

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

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

Getan.

Dung
quelle
Können Sie das weiter erklären? Was genau hat mit der anderen Lösung nicht funktioniert - gibt es Fehlermeldungen?
Nico Haase
Er hat ParameterBagInterface $ params in seinem Konstruktor verwendet, aber um die Parameterkonfiguration in services.yaml vollständig zu nutzen, habe ich die Abhängigkeitsinjektion verwendet.
Mist
Können Sie das weiter erklären? Die Antwort von richsage enthält nicht das ParameterBagInterface, sondern eine Liste der Parameter, die eingefügt werden sollen, genau wie Ihr Code
Nico Haase
Meine Antwort wurde 2012 veröffentlicht, als das Ökosystem nur noch Symfony 2 war. Ich verwende Symfony nicht mehr und habe es für nachfolgende Versionen nicht aktualisiert.
Richsage
-1

Symfony 3.4 hier.

Nach einigen Recherchen halte ich es nicht für eine gute Idee, Parameter über den Konstruktor an eine Klasse / einen Dienst zu übergeben. Stellen Sie sich vor, Sie müssen mehr Parameter als 2 oder 3 an eine Steuerung / einen Dienst übergeben. Was dann? Es wäre lächerlich, bis zu 10 Parameter zu übergeben.

Verwenden Sie stattdessen die ParameterBagKlasse als Abhängigkeit, wenn Sie den Dienst in yml deklarieren, und verwenden Sie dann so viele Parameter, wie Sie möchten.

Ein konkretes Beispiel: Nehmen wir an, Sie haben einen Mailer-Dienst wie PHPMailer und möchten die PHPMailer-Verbindungsparameter in der paramters.ymlDatei haben:

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

Ich denke, das ist ein besserer Weg.

Dan Costinel
quelle
-1

In Symfony 4 können wir über die Abhängigkeitsinjektion auf die Parameter zugreifen:

Dienstleistungen:

   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'
Schatten3002
quelle
Der bereitgestellte Code verwendet DI nicht richtig - das Injizieren des gesamten Containers wird als schlechter Stil angesehen, da Sie die tatsächlichen Abhängigkeiten verbergen
Nico Haase
Ich denke, Sie verwechseln die Konzepte, im Beispiel zeige ich nur einen allgemeinen Fall. Konsultieren Sie im Zweifelsfall die offizielle Symfony-Dokumentation, bevor Sie eine Abstimmung abgeben. symfony.com/doc/current/components/dependency_injection.html
Shades3002
Können Sie das weiter erklären? In der verknüpften Dokumentation heißt es eindeutig, dass das Injizieren des Containers keine gute Idee ist, und es wird kein Beispiel gezeigt, das diese Art der Injektion verwendet - da Sie beim Einspritzen des gesamten Containers keine Abhängigkeiten injizieren
Nico Haase