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"
Related
I'm trying to install FosMessageBundle, without FosUserBundle.
For that, in the doc, we have to create a new service and use it into our services.yaml file
But in the doc, their declaration is in XML, and I can't translate him to YAML:
<!-- app/config/services.xml -->
<service id="app.user_to_username_transformer" class="AppBundle\Form\DataTransformer\UserToUsernameTransformer">
<argument type="service" id="doctrine" />
</service>
<service id="fos_user.user_to_username_transformer" alias="app.user_to_username_transformer" />
I tried this :
app.user_to_username_transformer:
class: 'App\Form\DataTransformer\UserToUsernameTransformer'
arguments:
type: "service"
id: "doctrine"
fos_user.user_to_username_transformer:
alias: "app.user_to_username_transformer"
But I don't know if it's good
in yaml a service type="service" is prefixed with #. So your xml definition translates to
app.user_to_username_transformer:
class: App\Form\DataTransformer\UserToUsernameTransformer
arguments:
- '#doctrine'
fos_user.user_to_username_transformer:
alias: "#app.user_to_username_transformer"
More details in the docs
But if you have autowire configured and your argument is typehinted properly with Doctrine\Bundle\DoctrineBundle\Registry all you need is to alias your transformer
fos_user.user_to_username_transformer:
alias: "#App\Form\DataTransformer\UserToUsernameTransformer"
This is a question about Symfony 4 autowiring
using only 'array' as a constructor argument type-hint. I give a specific case, but this may be helpful to others because this situation happens in several Symfony bundles.
This Symfony command asks for a password and encodes it:
php bin/console security:encode-password
The code for this command is in vendor/symfony/security-bundle/Command/UserPasswordEncoderCommand.php
In Symfony 4.2.3, this is the constructor for UserPasswordEncoderCommand:
class UserPasswordEncoderCommand extends Command
{
protected static $defaultName = 'security:encode-password';
private $encoderFactory;
private $userClasses;
public function __construct(EncoderFactoryInterface $encoderFactory, array $userClasses = [])
{
$this->encoderFactory = $encoderFactory;
$this->userClasses = $userClasses;
parent::__construct();
}
Symfony uses dependency injection to call the constructor, passing in automatically determined arguments. The above constructor first argument $encoderFactory is autowired using the type-hint EncoderFactoryInterface.
My question is: How is the second argument $userClasses autowired?
How does Symfony know what the array should contain? Debug print statements show the array contains a single value "App\Entity\User".
Here is my config/packages/security.yaml
security:
encoders:
App\Entity\User:
algorithm: argon2i
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
authenticators:
- App\Security\LoginFormAuthenticator
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
logout:
path: app_logout
# Where to redirect after logout
target: app_login
I think the following three files are important to answering this question.
vendor/symfony/security-bundle/Resources/config/console.xml contains:
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults public="false" />
<service id="security.command.user_password_encoder" class="Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand">
<argument type="service" id="security.encoder_factory"/>
<argument type="collection" /> <!-- encoders' user classes -->
<tag name="console.command" command="security:encode-password" />
</service>
</services>
</container>
Part of vendor/symfony/security-bundle/Resources/config/security.xml contains:
<service id="security.encoder_factory" alias="security.encoder_factory.generic" />
<service id="Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface" alias="security.encoder_factory" />
<service id="security.user_password_encoder.generic" class="Symfony\Component\Security\Core\Encoder\UserPasswordEncoder">
<argument type="service" id="security.encoder_factory"></argument>
</service>
<service id="security.password_encoder" alias="security.user_password_encoder.generic" public="true" />
<service id="Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface" alias="security.password_encoder" />
Part of vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php contains:
function load(array $configs, ContainerBuilder $container)
...
if (class_exists(Application::class)) {
$loader->load('console.xml');
$container->getDefinition('security.command.user_password_encoder')->replaceArgument(1, array_keys($config['encoders']));
}
I ask this question because I would like to use UserPasswordEncoderCommand.php as a starting point for a command to create an admin user in my database. This will give the first admin user permission to login using the web browser to
create other users.
I tried copying vendor/symfony/security-bundle/Command/UserPasswordEncoderCommand.php to src/Command/AddUserCommand.php and changing these lines:
< namespace Symfony\Bundle\SecurityBundle\Command;
---
> namespace App\Command;
< class UserPasswordEncoderCommand extends Command
---
> class AddUserCommand extends Command
< protected static $defaultName = 'security:encode-password';
---
> protected static $defaultName = 'app:add-user';
When I ran this command: php bin/console app:add-user
this error appeared:
There are no configured encoders for the "security" extension.
This happened because the constructor __construct(EncoderFactoryInterface $encoderFactory, array $userClasses = [])
was called with only one argument, so the second argument defaulted to an empty array.
It seems these files also need to be copied from vendor/symfony/security-bundle to somewhere under src/
DependencyInjection/SecurityExtension.php
Resources/config/console.xml
and be somehow modified. Which other files need to be copied to src/ ? The Symfony documentation under https://symfony.com is excellent, but I can not find where this situation is described.
Note that this command works:
php bin/console app:add-user mypassword "App\Entity\User"
but I do not want the user to have to type this. And this may change if security.yaml changes.
In this particular case the data is being assembled by the SecurityBundle as part of the configuration process. When the kernel is booted all bundles are collected and their respective Extensions, usually inside the DependencyInjection subfolder, will be registered. When the container is built it is passed through all these extension so they can modify the services or add their own. This is how Symfony collects the configs from inside the bundle, when the main application itself does not know where they are stored, but the bundle does.
Getting the user classes is done by Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension when processing the encoders-section of the security.yml. If you look at the encoders section of your security.yaml you notice that each Encoder is assigned to a class name. The method will then use this array to create the appropriate encoder for the value and keep the class name as key. This array is passed to the encoder factory in createEncoders. Afterwards it will take the same array from the configuration, get the names of each user class using array_keys() and replace the argument with offset 1, i.e. the $userClasses, for the command.
This concept is called semantic configuration and inside your own application this would probably be overkill as you have to define the Configuration and then write the logic by hand. Instead you would probably just define an array with the class names in the parameters-section of your services.yaml.
If you want to pass in other services, which can come from other bundles your bundle does not know about, there is another approach called CompilerPasses. In a CompilerPass you can collect all classes, e.g. with a specific tag, and then pass their references to a method or the constructor of your service. This is for example used inside Symfony to register EventListeners or make Commands available in the Console application.
From monolog.xml:
<service id="monolog.logger" parent="monolog.logger_prototype" public="false">
<argument index="0">app</argument>
</service>
<service id="logger" alias="monolog.logger" />
<service id="monolog.logger_prototype" class="%monolog.logger.class%" abstract="true">
<argument /><!-- Channel -->
</service>
How do I accomplish the same overriding of the 0th argument in Yaml?
My colleague created a bundle that allows you to easily convert from XML to YML and it came up with:
services:
monolog.logger:
public: false
arguments: { index_0: app }
monolog.logger_prototype:
class: %monolog.logger.class%
arguments: ['']
logger: #monolog.logger
Never would have guessed that.
One of defining logger with custom channel as service way:
# app/config/config.yml
monolog:
channels: [custom_channel]
When in a controller:
$logger = $this->get('monolog.logger.custom_channel');
or when defining a service:
services:
app.logger.custom_channel:
parent: monolog.logger.custom_channel
I would like to add some code inside the function authenticate() of the Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager.
I tried to create a child bundle of the security bundle.
And i redefined the service for security.authentication.manager in this bundle like that
<!-- Authentication related services -->
<service id="security.authentication.manager" class="%security.authentication.manager.class%" public="false">
<argument type="collection" />
</service>
But when i relad the page, the framework throw an exception: InvalidArgumentException: You must at least add one authentication provider.
I suppose it's becaue the dependecies are created inside the parent bundle configuation.
What i must do to get it work without redefine the whole security bundle ?
Thank you.
I suppose that better would be created your own handlers.
You need to create service
Register it into service container.
Simply set handlers in your security.yml:
form_login:
success_handler: success_login_handler
failure_handler: failure_login_handler
logout:
success_handler: success_logout_handler
Ok, I found that the best way is to create my own a factory, that extends the formFactory of symfony2
Then i must create my own AuthentificationProvider that extend the DaoAuthenticationProvider and declare it as abstract service.
Then create the service inside the factory via the method createAuthProvider() and replace the parameters we need.
protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
{
$provider = 'acme.authentication.provider.dao.'.$id;
$container
->setDefinition($provider, new DefinitionDecorator('acme.authentication.provider.dao'))
->replaceArgument(0, new Reference($userProviderId))//replace args on the service constructor
->replaceArgument(2, $id)
->addArgument(new Reference('acme.api'))//add some args to the service constructor
;
return $provider;
}
What is the best way to have an event that fires after a record is inserted in Symfony2 / Doctrine?
First, register a service as a Doctrine event listener:
app/config.yml:
services:
foo.listener:
class: Vendor\FooBundle\BarClass
tags:
- { name: doctrine.event_listener, event: postPersist, method: onPostPersist }
Then in your listener class, define an onPostPersist method (or whatever you named the method in the config) that takes a Doctrine\ORM\Event\LifecycleEventArgs argument:
public function onPostPersist(LifecycleEventArgs $eventArgs)
{
// do stuff with the entity here
}
Note that you can't pass an instance of EntityManager to the listener class, because $eventArgs contains a reference to it, and doing so will throw a CircularReferenceException.
Doctrine Project documentation here. Symfony Project documentation here (out of date, but included for reference)/
Try injecting the container itself instead of the security context. with FOS_USER, security.context depends on your listener (EM) and your listener requires security.context.
<service id="foo.listener" class="%foo.listener.class%">
<argument type="service" id="service_container"/>
<tag name="doctrine.event_listener" event="postPersist" method="fooMethod" />
</service>
By the way, at least in XML, the method name does not seem to be working, by default it call the method 'postPersist' instead and ignore whatever method name you give (fooMethod); Please let me know if that's the case with YAML config, too, or I am just wrong.