In my notification service I have to send the notifications by mail, but in dev I want to send all the email to a specific adress:
if ( $this->container->get('kernel')->getEnvironment() == "dev" ) {
mail( 'mymail#mail.com', $lib, $txt, $entete );
} else {
mail( $to->getEmail(), $lib, $txt, $entete );
}
But the $this->container->get('kernel')->getEnvironment() works only in a controller.
I think I have to add an argument in my service constructor:
notification:
class: %project.notification.class%
arguments: [#templating, #doctrine]
But I didn't find any information about this.
There is no need to inject container. In fact, it is not a good idea to inject container because you're making your class dependent on the DI.
You should inject environment parameter:
services.yml
notification:
class: NotificationService
arguments: ["%kernel.environment%"]
NotificationService.php
<?php
private $env;
public function __construct($env)
{
$this->env = $env;
}
public function mailStuff()
{
if ( $this->env == "dev" ) {
mail( 'mymail#mail.com', $lib, $txt, $entete );
} else {
mail( $to->getEmail(), $lib, $txt, $entete );
}
}
For Symfony 4 you could do:
use Symfony\Component\HttpKernel\KernelInterface;
class SomeService
{
/**
* #var string
*/
private $environment;
/**
* Your Service constructor.
*/
public function __construct(KernelInterface $kernel)
{
$this->environment = $kernel->getEnvironment();
}
}
$this->environment now holds your environment like dev, prod or test.
In Symfony 4 (maybe 3.x, too) you can fetch the environment in a controller like this:
$env = $this->getParameter('kernel.environment');
(No explicit controller injection via services.yaml needed)
The reason you can get $this->container in a controller is because it is injected into the controller that you extend.
For example, you could inject in the container and set it up in your constructor.
services.yml
notification:
class: %project.notification.class%
arguments: [#templating, #doctrine]
NotificationService.php
<?php
private $container;
public function __construct()
{
$this->container = $container;
}
public function mailStuff()
{
if ( $this->container->get('kernel')->getEnvironment() == "dev" ) {
mail( 'mymail#mail.com', $lib, $txt, $entete );
} else {
mail( $to->getEmail(), $lib, $txt, $entete );
}
}
Have a look at dependency injection for more information.
PLEASE NOTE
Generally, injecting in the container is bad and means there is a better way to do something. In this case, Symfony already has the solution that we're trying to solve.
Enter SwiftMailer.
And specifically, the section on sending all dev emails to a set address.
Give it a go at setting up Swiftmailer and add the following to your dev config.
app/config/config_dev.yml
swiftmailer:
delivery_address: 'dev#example.com'
You can get all environments in .env file with $_ENV superglobal, thanks to dotenv
$_ENV['APP_ENV']
Actually you should not need to get the actual environment. For example if you need to send slack notifications only on production you can configure send_slack_notification parameter and set it to true/false in the different environments.
Then in your code you can do something like:
if ($parameters->get('send_slack_notification') == true) {
//send the notifications
}
By using this approach you can later turn on/off this setting in different environments without changing your implementation. Another benefit is that when you check your parameters file you can right away see all the options in the different environments and this will make debugging easier.
NOTE
KernelInterface $kernel->getEnvironment() can return both lowercase and uppercase results depending on how you've configured in your .env files. Another reason why you should't use it.
In symfony you can bind default value by $variableName.
config/services.yaml
services:
_defaults:
bind:
$kernelEnvironment: '%kernel.environment%'
NotificationService.php
private $env;
public function __construct($kernelEnvironment)
{
$this->env = $kernelEnvironment;
}
If you are into a static class and don't have access to the container, you always can do this inside the AppKernel.php->registerBundles():
$_ENV['APP_ENV'] = $this->getEnvironment();
That way you will always have the environment inside the superglobal $_ENV.
It's kinda a hack, but works like a charm.
I would like to have a return of my code.
.env.local :
API_KEY_TOTO="azerty"
But it is possible to pass the environment variable in the service.yml file. Here is an example
config/services.yaml
App\Service\MyService:
bind:
$apiKeyToto: '%env(API_KEY_TOTO)%'
App\Service\MyService.php :
class MyService {
private $client;
private $apiKeyToto;
public function __construct(HttpClientInterface $client, string $apiKeyToto)
{
$this->client = $client;
$this->apiKeyToto= $apiKeyToto;
}
public function getAllRepository(): array
{
return $this->getApi('/users/{username}/repos?&type=all&direction=desc&per_page=100');
}
private function getApi(string $url)
{
$response = $this->client->request('GET', 'https://api.github.com' . $url, [
'auth_basic' => ['username', $this->apiKeyToto],
]
);
return $response->toArray();
}
}
If you have any other suggestions.
If you use the Symfony Mailer component to send emails. You don't need to get the environment. Just define a new env variable that contains your specific address and put it in the EnvelopeListener
service.yaml
mailer.set_recipients:
class: Symfony\Component\Mailer\EventListener\EnvelopeListener
tags: ['kernel.event_subscriber']
arguments:
$sender: null
$recipients: '%env(json:ALL_MAIL_RECIPIENT)%'
.env
ALL_MAIL_RECIPIENT='[]'
.env.local
ALL_MAIL_RECIPIENT='["some#example.com"]'
Related
I have been looking at the Symfony 4.1 documentation on using the Swift_mailer. However, it appears the documentation is only assumed it being used in the Controller classes. I'm trying to create a Service with some reusable functions that send email.
I created a EmailService.php file in my service directory. When creating a new instance of this service, it quickly throws and error:
"Too few arguments to function
App\Service\EmailService::__construct(), 0 passed in
*MyApp\src\Controller\TestController.php on line 33
and exactly 1 expected"
I'm not sure how to pass \Swift_Mailer $mailer into the __construct correctly? I have auto wiring enabled in the services.yaml, so i'm not sure what I need to do differently?
class EmailService
{
private $from = 'support#******.com';
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
How do I pass the \Swift_Mailer into this EmailService construct?
I tried adding this to my config\services.yaml with no success:
App\Service\EmailService:
arguments: ['#mailer']
As mentioned by dbrumann in a comment, I needed to follow the proper way of injecting services.
First, I needed to add the services to config/services.yaml
#config/services.yaml
emailservice:
class: App\Service\EmailService
arguments: ['#swiftmailer.mailer.default', '#twig']
public: true
Second, I need to setup the service to accept both the mailer, and twig for rendering the template.
#App/Service/EmailService.php
<?php
namespace App\Service;
class EmailService
{
private $from = 'support#*****.com';
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer, \Twig\Environment $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
public function userConfirmation(string $recipient, string $confCode) : bool
{
$message = (new \Swift_Message())
->setSubject('Some sort of string')
->setFrom($this->from)
->setTo($recipient)
->setBody(
$this->templating->render(
'email/UserConfirmation.html.twig',
array('confCode' => $confCode)
),
'text/html'
)
/*
* If you also want to include a plaintext version of the message
->addPart(
$this->renderView(
'emails/UserConfirmation.txt.twig',
array('confCode' => $confCode)
),
'text/plain'
)
*/
;
return $this->mailer->send($message);
}
}
Third, to call it from the controller, make sure your controller is extending Controller and not the AbstractController! Crucial step!! Here is an example based on the parameters I require in my service:
public function userConfirmation()
{
$emailService = $this->get('emailservice');
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
I hope this helps people. AbstractController does not give you the proper access to the service containers.
#config/services.yaml
App\Service\EmailService
arguments: ['#swiftmailer.mailer.default']
public: true
And in your controller :
public function userConfirmation(EmailService $emailService)
{
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
Use FQCN "App\Service\MyService" to declare services in services.yaml and a proper legacy_aliases.yaml file to declare legacy aliases like "app.service.my.service" it helps keep your services.yaml clean...
I can't figure out how to impersonate a user by user's id instead of user's username in Symfony?
The following trick which works with username can't work with id, as symfony is looking for username:
?_switch_user={id}
This is impossible to do without implementing your own firewall listener, as behind the scenes it loads the user from the userprovider (which only has a loadUserByUsername() method in its interface).
You could however implement your own firewall listener and get inspired by having a look at the code in Symfony\Component\Security\Http\Firewall\SwitchUserListener. For detailed information on implementing your own authentication provider, check the cookbook article.
EDIT:
One possible solution might be registering an extra request listener:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LookupSwitchUserListener implements EventSubscriberInterface
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['lookup', 12] // before the firewall
];
}
public function lookup(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->has('_switch_user') {
return; // do nothing if already a _switch_user param present
}
if (!$id = $request->query->has('_switch_user_by_id')) {
return; // do nothing if no _switch_user_by_id param
}
// lookup $username by $id using the repository here
$request->attributes->set('_switch_user', $username);
}
}
Now register this listener in the service container:
services:
my_listener:
class: LookupSwitchUserListener
tags:
- { name: kernel.event_subscriber }
Calling a url with the ?_switch_user_by_id=xxx parameter should now correctly look up the username and set it so the SwitchUserListener can switch to the specified user.
I have a single app that ca serve multiple domains.
I'm having a problem with the framework.session.cookie_domain
I'd like the session to be kept between subdomain, so far so good with cookie_domain set right
Where i have a problem is that i'd like the cookie_domain parameter set dynamically as i don't know in advance which domain the request is coming from.
I tried in the AppKernel.php to do something like :
$domain = substr($_SERVER['HTTP_HOST'], strpos($_SERVER['HTTP_HOST'], '.'));
ini_set('session.cookie_domain', $domain);
But it seems to break my sessions
I could have multiple config.yml one for each domain but i'd like to avoid that.
Do you know a way?
Thanks
I have a similar situation. It's a multi-tenant site with school districts and schools. Each district and school has its own URL as follows:
school-1.district-1.example.com
school-2.district-1.example.com
school-1.district-2.example.com
I want users to be able to access all schools in one district with a single login. I therefore need the cookie to be at the district level.
This is my session storage service.
namespace AppBundle\Services;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
class MySessionStorage extends NativeSessionStorage
{
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null, RequestStack $requestStack)
{
$host = $requestStack->getMasterRequest()->getHost();
$options['cookie_domain'] = substr($host, strpos($host, '.') + 1);
parent::__construct($options, $handler, $metaBag);
}
}
In services.yml
mySessionStorage:
class: AppBundle\Services\MySessionStorage
arguments: [%session.storage.options%, #session.handler, #session.storage.metadata_bag, #request_stack]
In config.yml under framework:
session:
handler_id: session.handler.native_file
storage_id: mySessionStorage
Note that handler_id is null (~) by default in a standard Symfony installation. It needs to be set to something for the service to receive a non-null #session.handler.
That does it for the session cookie but the other one I needed to change is the remember_me cookie. You can set the domain to a constant in config.yml but I need it to depend on host. Maybe I'm missing something but I couldn't see a way to do it dynamically within the security system. RememberMeFactory is directly instantiated, not via configuration. My solution is to listen for kernel.response and replace the cookie before it is sent.
namespace AppBundle\Listeners;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class CookieFix
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function onKernelResponse(FilterResponseEvent $event)
{
$response = $event->getResponse();
$cookies = $response->headers->getCookies();
$rMe = null;
foreach($cookies as $cookie) {
/** #var \Symfony\Component\HttpFoundation\Cookie $cookie */
if ($cookie->getName() == 'REMEMBERME') {
$rMe = $cookie;
break;
}
}
if ($rMe !== null) {
$host = $this->requestStack->getMasterRequest()->getHost();
$newDomain = substr($host, strpos($host, '.') + 1);
$response->headers->removeCookie($rMe->getName());
$response->headers->setCookie(new Cookie($rMe->getName(), $rMe->getValue(), $rMe->getExpiresTime(), $rMe->getPath(), $newDomain));
}
}
}
I should probably try to get the cookie name from the config.
In services.yml
cookieFix:
class: AppBundle\Listeners\CookieFix
arguments: [#request_stack]
tags:
- { name: kernel.event_listener, event: kernel.response, method: onKernelResponse, priority: -100 }
The -100 priority ensures that it runs after the listener that creates the cookie.
Ok, i've figured this out.
It was not that difficult.
I created a custom sessionStorage, extending the default one and i did a simple override where the options were being dealt with: there i calculated my cookie_domain and passed it to the parent::function :
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
/**
* DynamicDomainSessionStorage.
*
* #author Julien Devouassoud
*/
class DynamicDomainSessionStorage extends NativeSessionStorage
{
/**
* setOptions.
*
* {#inheritDoc}
*/
public function setOptions(array $options)
{
if(isset($_SERVER['HTTP_HOST'])){
$domain = substr($_SERVER['HTTP_HOST'], strpos($_SERVER['HTTP_HOST'], '.'));
$options["cookie_domain"] = $domain;
}
return parent::setOptions($options);
}
}
Don't forget:
• to declare your class as a service
• set this service as storage
• set the save_path otherwise cookie_domain seems not to work (breaks the session)
• i set a 'name' as well but i don't think it's essential
• code config.yml :
#...
framework:
#...
session:
storage_id: v3d.session.storage.dynamic_domain
save_path: %kernel.root_dir%/cache/var/sessions
name: SFSESSID
services
v3d.session.storage.dynamic_domain:
class: V3d\Bundle\ApplicationBundle\Services\DynamicDomainSessionStorage
UPDATE:
In case you need to work with Entity Manager in a custom class, you could go this way:
put this code in your bundle:
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
require_once DIR . '/../../../app/bootstrap.php.cache';
require_once DIR . '/../../../app/AppKernel.php';
class ApplicationBoot {
private static $kernel;
public static function getContainer() {
if(self::$kernel instanceof \AppKernel) {
if(!self::$kernel->getContainer() instanceof Container){
self::$kernel->boot();
}
return self::$kernel->getContainer();
}
$environment = 'prod';
if (!array_key_exists('REMOTE_ADDR', $_SERVER) || in_array(#$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', 'localhost'))) {
$environment = 'dev';
}
self::$kernel = new \AppKernel($environment, false);
self::$kernel->boot();
return self::$kernel->getContainer();
}
public static function shutDown() {
self::$kernel->shutdown();
}}
So now you can access EntityManager:
$container = ApplicationBoot::getContainer();
$entityManager = $container->get('doctrine')->getEntityManager();
I have not seen a service file like this:
arguments:
entityManager: "#doctrine.orm.entity_manager"
Probably should be:
arguments: [#doctrine.orm.entity_manager]
UPDATE:
Based on some comments it appears that you are trying to do:
$job = new PostJob();
And expecting that entity manager will somehow be passed. And that is just not the way things work. You need to do:
$job = $this->get('postjob.service.id');
In order to have the Symfony 2 dependency injection work. Review the chapter in the manual on services. It might seem a bit over whelming at first but once you get a few services working then it becomes second nature.
To load the services.yml from your bundle, you need to provide an extension class:
// src/Vendor/YourBundle/DedendencyInjection/VendorYourBundleExtension.php
namespace Vendor\YourBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension,
Symfony\Component\DependencyInjection\ContainerBuilder,
Symfony\Component\DependencyInjection\Loader\YamlFileLoader,
Symfony\Component\Config\FileLocator;
class VendorYourBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
I've got an Entity that I want to associate with the users session.
I created a service so that I could reach this info from where ever.
in the service i save the entities id in an session variable
and in the getEntity() method i get the session variable and with doctrine find the entity and return it.
this way to the template i should be able to call {{ myservice.myentity.myproperty }}
The problem is that myservice is used all over the place, and I don't want to have to get it in every since Action and append it to the view array.
Is there a way to make a service accessible from all views like the session {{ app.session }} ?
The solution
By creating a custom service i can get to that from where ever by using
$this->get('myservice');
this is all done by http://symfony.com/doc/current/book/service_container.html
But I'll give you some demo code.
The Service
This first snippet is the actual service
<?php
namespace MyBundle\AppBundle\Extensions;
use Symfony\Component\HttpFoundation\Session;
use Doctrine\ORM\EntityManager;
use MyBundle\AppBundle\Entity\Patient;
class AppState
{
protected $session;
protected $em;
function __construct(Session $session, EntityManager $em)
{
$this->session = $session;
$this->em = $em;
}
public function getPatient()
{
$id = $this->session->get('patient');
return isset($id) ? $em->getRepository('MyBundleStoreBundle:Patient')->find($id) : null;
}
}
Register it in you config.yml with something like this
services:
appstate:
class: MyBundle\AppBundle\Extensions\AppState
arguments: [#session, #doctrine.orm.entity_manager]
Now we can as I said before, get the service in our controllers with
$this->get('myservice');
But since this is a global service I didn't want to have to do this in every controller and every action
public function myAction()
{
$appstate = $this->get('appstate');
return array(
'appstate' => $appstate
);
}
so now we go create a Twig_Extension
Twig Extension
<?php
namespace MyBundle\AppBundle\Extensions;
use MyBundle\AppBundle\Extensions\AppState;
class AppStateExtension extends \Twig_Extension
{
protected $appState;
function __construct(AppState $appState) {
$this->appState = $appState;
}
public function getGlobals() {
return array(
'appstate' => $this->appState
);
}
public function getName()
{
return 'appstate';
}
}
By using dependency injection we now have the AppState Service that we created in the twig extension named appstate
Now we register that with the symfony (again inside the services section inside the config-file)
twig.extension.appstate:
class: MyBundle\AppBundle\Extensions\AppStateExtension
arguments: [#appstate]
tags:
- { name: twig.extension }
The important part being the "tags", since this is what symfony uses to find all twig extensions
We are now set to use our appstate in our twig templates by the variable name
{{ appstate.patient }}
or
{{ appstate.getPatient() }}
Awesome!
Maybe you can try this in your action ? : $this->container->get('templating')->addGlobal($name, $value)