¿Cómo acceder a los parámetros de una aplicación desde un servicio?

81

Desde mis controladores, accedo a los parámetros de la aplicación (aquellos en /app/config) con

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

Pero no sé cómo acceder a él desde un servicio (imagino que no se supone que mi clase de servicio se extienda Symfony\Bundle\FrameworkBundle\Controller\Controller).

¿Debería asignar los parámetros necesarios en el registro de mi servicio de esta manera:

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

¿o algo similar? ¿Cómo debo acceder a los parámetros de mi aplicación desde un servicio?


Esta pregunta parece la misma, pero la mía en realidad la responde (parámetros de un controlador), estoy hablando de acceder desde un servicio.

Pierre de LESPINAY
fuente
Mi pregunta en realidad responde a esta (parámetros de un controlador), estoy hablando de acceder desde un servicio aquí
Pierre de LESPINAY
No estoy seguro de entenderte. ¿Estás de acuerdo con el duplicado? Los controladores son servicios en Symfony hoy en día.
Tomáš Votruba
No estoy de acuerdo con el duplicado. La otra pregunta es específicamente para controladores con los que se obtienen parámetros fácilmente $this->getParameter().
Pierre de LESPINAY
Eso es cierto, estoy de acuerdo. Y todavía es posible. También existe una tendencia a alejarse del contenedor que se inyecta en cualquier lugar y pasar a la inyección del constructor. Gracias al descubrimiento automático del servicio PSR-4 y al enlace de parámetros: symfony.com/blog/new-in-symfony-3-4-local-service-binding , es limpio y mucho más corto para trabajar.
Tomáš Votruba

Respuestas:

123

Puede pasar parámetros a su servicio de la misma manera que inyecta otros servicios, especificándolos en su definición de servicio. Por ejemplo, en YAML:

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

donde %my_param1%etc corresponde a un parámetro llamado my_param1. Entonces su constructor de clase de servicio podría ser:

public function __construct($myParam1, $myParam2)
{
    // ...
}
richsage
fuente
¿Hay alguna manera de manejar en caso de que el parámetro no exista? en lugar del IOC de excepción de Symfony.
Mohammed Yassine CHABLI
y de donde my_param1viene el valor de ?
Sliq
@Sliq, lo define en parameters.yml
Gráfico
35

El camino limpio 2018

Desde 2018 y Symfony 3.4 hay una forma mucho más limpia: fácil de configurar y usar.

En lugar de utilizar un anti-patrón de contenedor y localizador de servicios / parámetros, puede pasar parámetros a la clase a través de su constructor . No se preocupe, no se trata de un trabajo que requiera tiempo, sino de configurar una vez y olvidar el enfoque.

¿Cómo configurarlo en 2 pasos?

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

¡Actualización instantánea lista!

En caso de que utilice un enfoque anterior, puede automatizarlo con Rector .

Lee mas

Esto se denomina enfoque de localizador de inyección de constructor sobre servicios .

Para leer más sobre esto, consulte mi publicación Cómo obtener parámetros en el controlador Symfony de forma limpia .

(Está probado y lo mantengo actualizado para la nueva versión principal de Symfony (5, 6 ...)).

Tomáš Votruba
fuente
1
Habría tomado algo más que una clase de controlador como ejemplo de código, ya que OP quiere inyectar parámetros en cualquier servicio y el cableado automático está habilitado de forma predeterminada en los controladores
SF3
Gracias por tu comentario. La configuración anterior funciona para cualquier servicio, controlador, repositorio o servicio propio. No hay diferencia.
Tomáš Votruba
18

En lugar de mapear los parámetros necesarios uno por uno, ¿por qué no permitir que su servicio acceda al contenedor directamente? Al hacerlo, no tiene que actualizar su mapeo si se agregan nuevos parámetros (que se relacionan con su servicio).

Para hacerlo:

Realice los siguientes cambios en su clase de servicio

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

Agregue @service_container como "argumentos" en su services.yml

services:
  my_service_id:
    class: ...\MyServiceClass
    arguments: ["@service_container"]  // <- Add this
Jamón L.
fuente
1
Exactamente lo que estaba buscando, por eso me gusta la inyección de dependencia :)
klimpond
44
-1. Pasar el contenedor en su totalidad frustra el propósito de la inyección de dependencia. Su clase solo debe recibir lo que realmente necesita para funcionar, no todo el contenedor.
Richsage
@richsage, ¿hay alguna alternativa para lograr resultados similares, por lo que la declaración de servicio no se actualiza para cada parámetro? Esto también se ve un poco más ordenado que inyectar parámetros uno por uno.
Batandwa
1
Pasar un contenedor completo a un servicio es una muy mala idea. Como dice @richsage, no se ajusta al propósito de inyección de dependencia. Si no desea usar la inyección de dependencia, entonces no use Symfony2 :)
tersakyan
2
@tersakyan, pero ¿qué pasa con los controladores entonces? por defecto, todos los controladores tienen acceso al controlador. Entonces, ¿no deberíamos usar controladores también? :)
Alex Zheka
9

Hay una nueva forma muy limpia de lograrlo desde 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');
...
 }
}

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

Ousmane
fuente
6

Como solución a algunos de los problemas mencionados, defino un parámetro de matriz y luego lo inyecto. Agregar un nuevo parámetro más tarde solo requiere agregarlo a la matriz de parámetros sin ningún cambio en los argumentos o la construcción de service_container.

Entonces, extendiendo la respuesta de @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%]

Luego acceda al interior de la clase

public function __construct($params)
{
    $this->param1 = array_key_exists('param_name_1',$params)
        ? $params['param_name_1'] : null;
    // ...
}
Dave
fuente
En el momento de escribir este comentario, desafortunadamente, la anidación de parámetros no es posible en Symfony, consulte los documentos: symfony.com/doc/current/service_container/…
Tomáš Votruba
5

Con Symfony 4.1 la solución es bastante simple.

Aquí hay un fragmento de la publicación original:

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

Enlace a la publicación original: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service

Carol-Theodor Pelu
fuente
0

@richsage es correcto (para Symfony 3.?) pero no funcionó para mi Symfony 4.x. Así que aquí está Symfony 4.

en el archivo services.yaml

parameters:
    param1: 'hello'

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

en su archivo de clase de servicio rutinaCheck.php, haga el constructor así

private $toBechecked;

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

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

Hecho.

Estiércol
fuente
¿Puedes explicarlo más? ¿Qué no funcionó exactamente con la otra solución? ¿Hay algún mensaje de error?
Nico Haase
Usó ParameterBagInterface $ params en su constructor, pero para utilizar completamente la configuración de parámetros en services.yaml usé la inyección de dependencia.
Estiércol
¿Puedes explicar eso más? La respuesta de Richsage no contiene ese ParameterBagInterface, sino una lista de parámetros que se inyectarán, al igual que su código
Nico Haase
Mi respuesta fue publicada en 2012, cuando el ecosistema era solo Symfony 2. Ya no uso Symfony, así que no he actualizado para versiones posteriores.
Richsage
-1

Symfony 3.4 aquí.

Después de algunas investigaciones, no creo que pasar parámetros a una clase / servicio a través de su constructor sea siempre una buena idea. Imagínese si necesita pasar a un controlador / servicio algunos parámetros más que 2 o 3. ¿Entonces qué? Sería ridículo pasar, digamos, hasta 10 parámetros.

En su lugar, use la ParameterBagclase como una dependencia, cuando declare el servicio en yml, y luego use tantos parámetros como desee.

Un ejemplo concreto, digamos que tiene un servicio de correo, como PHPMailer, y desea tener los parámetros de conexión de PHPMailer en el paramters.ymlarchivo:

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

Creo que esta es una forma mejor.

Dan Costinel
fuente
-1

En Symfony 4, podemos acceder a los parámetros mediante inyección de dependencias:

Servicios:

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

parámetros.yml:

parameters:
     upload_directory: '%kernel.project_dir%/public/uploads'
sombras3002
fuente
El código proporcionado no usa DI correctamente - inyectar todo el contenedor se considera de mal estilo, ya que oculta las dependencias reales
Nico Haase
Creo que te estás equivocando de conceptos, en el ejemplo solo muestro un caso general. En caso de duda, consulte la documentación oficial de Symfony antes de emitir un voto. symfony.com/doc/current/components/dependency_injection.html
shades3002
¿Puedes explicar eso más? La documentación vinculada establece claramente que inyectar el contenedor no es una buena idea, y no muestra ningún ejemplo que use este tipo de inyección; tan claramente, no está inyectando dependencias cuando inyecta todo el contenedor
Nico Haase