Questions about Symfony 2 service container parameters - symfony

1. Passing parameters into the service?
If not already defined in the services.xml (or yaml), is the only way to pass parameter(s) into service is:
$container->setParameter('loader', $loader);
$container->get('myservice');
I suppose this way loader will be available to ALL services, not just "myservice"?
2. Passing an array of objects into the service?
The Template/DelegatingEngine class takes an array of engine object into the constructor, and I dont know how should I define that in the xml file:
public function __construct(array $engines = array())
{
$this->engines = array();
foreach ($engines as $engine) {
$this->addEngine($engine);
}
}
What should I put into the
<service id="myCustomeFramework.TemplateEngine" class="path\to\DelegateEngine" scope="prototype">
<argument>how can i pass an array of engines here?</argument>
</service>

Answer 1
Yes, it will be available for all services that uses that parameter and that are called after setting the parameter.
Answer 2
For passing an array as an argument to a service using xml you have to do it in this way:
<service id="myCustomeFramework.TemplateEngine" class="path\to\DelegateEngine" scope="prototype">
<argument type="collection">
<argument key="key">value</argument>
<argument key="key">value</argument>
<argument key="key">value</argument>
</argument>
</service>

Related

Inject entity repository as dependency in Symfony

I have a Symfony project where I want to inject an entity repository into a service. The service definition is in XML format.
<service id="vendorname_shop.checkout_data_manager" class="Vendorname\ShopBundle\Checkout\CheckoutDataManager">
<argument type="service" id="security.token_storage" />
<argument type="service" id="session" />
<argument type="service" id="vendorname_shop.repository.pickup_point" />
<argument type="service" id="vendorname_shop.repository.order_payment_method" />
<argument type="service" id="vendorname_shop.repository.billing_address" />
</service>
I'd like to make vendorname_shop.repository.billing_address service to be a simple entity repository (not a custom class that I wrote, but the result of
EntityManager->getRepository(Vendorname\ShopBundle\Entity\BillingAddress::class)
method call), so I used the factory syntax in the xml, but I keep recieving error messages when Symfony tries to evaluate the argument:
<service id="vendorname_shop.repository.billing_address" class="Doctrine\ORM\EntityRepository">
<factory service="doctrine.orm.entity_manager" method="getRepository" />
<argument type="expression">Vendorname\ShopBundle\Entity\BillingAddress::class</argument>
</service>
The code above gives me Unexpected character "\" around position 11.
You can try something like this:
services:
my_service_name:
class: AppBundle\Controller\MyServiceName
arguments: ["#=service('doctrine.orm.entity_manager').getRepository('AppBundle:MyEntity')"]
Then you have build a service for your repository.
public function __construct(MyEntityRepository $repository) {
$this->repository = $repository;
}
But i think there are a lot more possibilities.
http://www.zuellich.de/blog/2016/03/symfony-3-inject-entity-repository-into-service-controller.html
here is another solution. I've replaced my answer with some of that solution it is a bit better.
As Cerad said, if I use a fully qualified name, the ::class is totally useless!
In addition changing the argument type to string solved the problem!
<argument type="string">Vendorname\ShopBundle\Entity\BillingAddresss</argument>

Override TranslationsCacheWarmer service

I have an application that contains a lot of translation resources for a lot of different languages. The warmup process takes a long time because of this.
I only support the translation of my site in a few languages, so I'd like to avoid generating catalogues for all the languages that I don't support.
What I did:
I overrode the TranslationsCacheWarmer to use my own translator. This is a custom translator that decorates the default translator but overrides the warmup method to only warmup files that are part of the locales that I support.
The problem is that the default warmer still runs generating files for all the locales.
This is the code that contains the custom translator: https://gist.github.com/marcosdsanchez/e8e2cd19031a2fbcd894
and here's how I'm defining the services:
<service id="web.translation.public_languages_translator" class="X\Translation\PublicLanguagesTranslator" public="false">
<argument type="service" id="translator.default" />
<argument type="collection">%chess.translation.public_languages%</argument>
</service>
<service id="translation.warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer" public="false">
<argument type="service" id="web.translation.public_languages_translator" />
<tag name="kernel.cache_warmer" />
</service>
I'm using symfony 2.7.3
I ended up doing something different to get the same result. Instead of trying to create a custom CacheWarmer, I created a compiler pass and modified the definition of the 'options' argument. In this compiler pass, I removed all the files that don't have the locale or language code.
Code:
<?php
namespace X\DependencyInjection\Compiler;
use X\Entity\I18nLanguage;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TranslatorCompilerPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*
* #param ContainerBuilder $container
*
* #api
*/
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('translator.default');
$options = $definition->getArgument(3);
$keys = array_keys($options['resource_files']);
$locales = I18nLanguage::PUBLIC_LOCALES;
$langCodes = array();
foreach (I18nLanguage::PUBLIC_LOCALES as $locale) {
$langCodes[] = substr($locale, 0, strpos($locale, '_'));
}
$localesAndLangCodes = array_merge($locales, $langCodes);
foreach ($keys as $key) {
if (!in_array($key, $localesAndLangCodes, true)) {
unset($options['resource_files'][$key]);
}
}
$arguments = $definition->getArguments();
$definition->setArguments(array($arguments[0], $arguments[1], $arguments[2], $options));
}
}
That did the trick for me and I can also apply other optimizations like the removal of loaders, etc.

Why I always get the message "You cannot dump a container with parameters" in symfony console?

In Terminal when I try to run my created command I get the following error:
[Symfony\Component\DependencyInjection\Exception\InvalidArgumentException]]
You cannot dump a container with parameters that contain references to other services (reference to service "old_sound_rabbit_mq.split_file_producer" found in "/rabbit").
This is what happens when I run my newly created console command:
$this->getContainer()->get('split_file')->process();
I don't know why it says that You cannot dump! I don't dump anything in the project.
Is there something I'm unaware of?
EDIT
A part of my services.yml:
<parameters>
<parameter key="file_path">/var/www/path/file.xml</parameter>
<parameter key="rabbit" type="service" id="old_sound_rabbit_mq.split_file_producer" />
</parameters>
<service id="split_file" class="Acme\DemoBundle\SplitFile">
<argument>%file_path%</argument>
<argument>%rabbit%</argument>
</service>
And this is my console command class:
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ReadFileCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('reader:read-file');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->getContainer()->get('split_file')->process();
}
}
Parameters only have a key value. You can't add service etc to a parameter.
<service id="split_file" class="Acme\DemoBundle\SplitFile">
<argument>%file_path%</argument>
<argument type="service" id="old_sound_rabbit_mq.split_file_producer"/>
</service>
http://symfony.com/doc/current/book/service_container.html
I'd suggest switching to yml as it's less verbose
You could create an alias for old rabbit if you wanted.

Inject SwiftMailer into symfony2 service

I have a service which extends UserManager, so when I do:
$message = \Swift_Message::newInstance()
->setSubject('~')
->setFrom('~')
->setTo('~')
->setBody('~', 'text/html');
$this->get('mailer')->send($message);
I get the error:
Fatal error: Call to undefined method My\MyBundle\Service\ServiceClass::get()
I know this is because I need to inject the swiftmailer into here, but how?
(usually the service class extends 'Generic' so the swift mailer is included.)
Depending on what kind of service file you are using you need to inject it into your service directly like you said.
XML:
<services>
<service id="sample.service" class="%sample.service.class%">
<argument type="service" id="mailer" />
</service>
</services>
YAML:
services:
sample.service:
class: %sample.service.class%
arguments: [#mailer]
You can simply grab the service in your constructor like this.
Or if you really want, you can inject the service_container. But that's really dirty, since you can just inject the services you need.
Injection the service_container is only needed if you need a dynamic service call.
In services.yml (symfony 4 example)
mailer:
class: \Swift_Mailer
myClass:
class: x\x
arguments:
- "#mailer"

Extending symfony mailer class in FOSUserBundle

Does anyone know how you can extend the Mailer class in the FOSUserBundle?
I am implementing a very basic parental email check (all the validation is done on the form to force a parent's email to be entered), if the parent email field is populated on the user entity then it shoudl send the email to that address not the user's email.
I have tried the following so far:
namespace SSERugby\UserBundle\Mailer;
use FOS\UserBundle\Mailer\Mailer as BaseMailer;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Routing\RouterInterface;
class Mailer extends BaseMailer
{
public function sendConfirmationEmailMessage(UserInterface $user)
{
$email = $user->getEmail();
$parentEmail = $user->getParentEmail();
if(isset($parentEmail)&&(trim($parentEmail)!='')){
$email = $parentEmail;
}
$template = $this->parameters['confirmation.template'];
$url = $this->router->generate('fos_user_registration_confirm', array('token' => $user->getConfirmationToken()), true);
$rendered = $this->templating->render($template, array(
'user' => $user,
'confirmationUrl' => $url
));
$this->sendEmailMessage($rendered, $this->parameters['from_email']['confirmation'], $email);
}
}
It seems to just ignore the overridden class though and use the default, i have cleared the cache before testing.
Thanks
You should create new service with extended mail class (in src\SSERugby\UserBundle\Resources\config\services.xml) like:
<service id="my_mailer" class="SSERugby\UserBundle\Mailer\Mailer" public="true">
<argument type="service" id="mailer" />
<argument type="service" id="router" />
<argument type="service" id="templating" />
<argument type="collection">
<argument key="confirmation.template">%fos_user.registration.confirmation.template%</argument>
<argument key="resetting.template">%fos_user.resetting.email.template%</argument>
<argument key="from_email" type="collection">
<argument key="confirmation">%fos_user.registration.confirmation.from_email%</argument>
<argument key="resetting">%fos_user.resetting.email.from_email%</argument>
</argument>
</argument>
</service>
and then in app/config/config.yml use this service as default:
fos_user:
# ...
service:
mailer: my_mailer
FYI: I copied all service's arguments from FOSUserBundle default mailer config. You can add your own params to it. Also you can read about custom mailers at https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/emails.md

Resources