I would like to access my database that contains all my user inside my provider with doctrine. I followed a tutorial (http://symfony.com/doc/current/security/custom_provider.html) to build my provider for my user, so I have an loadUserByUsername function :
public function loadUserByUsername($username)
{
// make a call to your webservice here
$player = new Player();
$player = $this->getDoctrine()
->getRepository('AppBundle:Player')
->findOneByPseudo($username);
// pretend it returns an array on success, false if there is no user
if ($player) {
return $player;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
But of course my getDoctrine() function is undefined. So there is something I don't understand with the provider, I am trying to use it to be authenticated when I login so I need a provider, but why I can't search inside my database? How should I write this function? Thank for your help
EDIT :
When I add doctrine by service.yml (and after writting my constructor inside my provider), I have this error :
FatalThrowableError in PlayerProvider.php line 13:
Type error: Argument 1 passed to AppBundle\Security\PlayerProvider::__construct() must be an instance of Doctrine\Bundle\DoctrineBundle\Registry, instance of Doctrine\ORM\EntityManager given, called in /home/jean/PW6/SkA/SkeletonsOnlineV2/skeleton-online/var/cache/dev/appDevDebugProjectContainer.php on line 327
EDIT 2 : When I just put arguments: ['#doctrine'] inside my service.yml, I get an error that says that doctrine is undefined
EDIT 3 : It works now, I just made a dumb mistake
If you read further, it says the following (emphasis mine):
The real implementation of the user provider will probably have some dependencies or configuration options or other services. Add these as arguments in the service definition.
So in your case it would be something like
# app/config/services.yml
services:
app.webservice_user_provider:
class: AppBundle\Security\User\WebserviceUserProvider
arguments: ['#doctrine']
And your class needs a constructor
class WebserviceUserProvider implements UserProviderInterface
{
protected $doctrine;
public function __construct (\Doctrine\Bundle\DoctrineBundle\Registry $doctrine)
{
$this->doctrine = $doctrine;
}
// ...
}
Then in your method replace $this->getDoctrine() with just $this->doctine
Related
I need to pass LoggerInterface to the MyGenerator used in #ORM\CustomIdGenerator(class=MyGenerator::class)
Doctrine does not use the symfony container to instantiate the generator and I'm ending up with an Exception Too few arguments to function How can I use the LoggerInterface in my id generator ?
Unfortunately, it's not possible to inject LoggerInterface into MyGenerator class, as it's not a service and has nothing to do with the service container. However, in AbstractIdGenerator there is an EntityManager available, which provides a foundation for a workaround solution in order to propagate logs via a database table. After that, you'll be able to fetch log messages from a table via cronjob and write proper logs or do whatever you need.
class MyGenerator extends AbstractIdGenerator
{
public function generate(EntityManager $em, $entity)
{
$identifier = '...'; // generate an identifier
// push a log message to a db
$query = $em->createQuery('INSERT INTO db.logger (id, message, created_at) VALUES (null, :message, NOW())');
$query->setParameter('message', 'Log message...');
$query->execute();
return $identifier;
}
}
I'm trying to get a simple "200 Response" test to work for a part of a website requiring an authenticated user. I think I've got the creation of the Session working, as during debugging the Controller function is called and a User is retrieved (using $this->getUser()).
However, afterwards the function fails with the following message:
1) App\Tests\Controller\SecretControllerTest::testIndex200Response
expected other status code for 'http://localhost/secret_url/':
error:
Multiple non-persisted new entities were found through the given association graph:
* A new entity was found through the relationship 'App\Entity\User#role' that was not configured to cascade persist operations for entity: ROLE_FOR_USER. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade
persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
* A new entity was found through the relationship 'App\Entity\User#secret_property' that was not configured to cascade persist operations for entity: test123. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade pe
rsist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). (500 Internal Server Error)
Failed asserting that 500 matches expected 200.
This would make sense if this was not already stored in the (MySQL) database and retrieved with Doctrine. The records are created using Fixtures on each run/for each test. This is why in the Controller $this->getUser() functions as expected.
The test I'm wanting to work:
public function testIndex200Response(): void
{
$client = $this->getAuthenticatedSecretUserClient();
$this->checkPageLoadResponse($client, 'http://localhost/secret_url/');
}
Get a user:
protected function getAuthenticatedSecretUserClient(): HttpKernelBrowser
{
$this->loadFixtures(
[
RoleFixture::class,
SecretUserFixture::class,
]
);
/** #var User $user */
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => 'secret_user']);
$client = self::createClient(
[],
[
'PHP_AUTH_USER' => $user->getUsername(),
'PHP_AUTH_PW' => $user->getPlainPassword(),
]
);
$this->createClientSession($user, $client);
return $client;
}
Create a session:
// Based on https://symfony.com/doc/current/testing/http_authentication.html#using-a-faster-authentication-mechanism-only-for-tests
protected function createClientSession(User $user, HttpKernelBrowser $client): void
{
$authenticatedGuardToken = new PostAuthenticationGuardToken($user, 'chain_provider', $user->getRoles());
$tokenStorage = new TokenStorage();
$tokenStorage->setToken($authenticatedGuardToken);
$session = self::$container->get('session');
$session->set('_security_<security_context>', serialize($authenticatedGuardToken));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
self::$container->set('security.token_storage', $tokenStorage);
}
This works for the creating of the client, session and cookie.
When the Request is executed to the $url in the first function, it gets into the endpoint, confirming the User is indeed authenticated.
According to the documentation here a User should be "refreshed" from via the configured provider (using Doctrine in this case) to check if a given object matches a stored object.
[..] At the beginning of the next request, it's deserialized and then passed to your user provider to "refresh" it (e.g. Doctrine queries for a fresh user).
I would expect this would also ensure that the session User is replaced with a Doctrine managed User object to prevent the error above.
How can I go about solving that the User in the session becomes a managed User during PhpUnit testing?
(Note: the production code works without any issue, this problem only arises during testing (legacy code now starting to get tests))
Ok, had multiple issues, but got it working doing the following:
First, was creating a Client using incorrect password, I was creating (in Fixtures) User entities with username and password being identical. The function getPlainPassword, though present in an interface, was not something stored, so was a blank value.
Corrected code:
$client = self::createClient(
[],
[
'PHP_AUTH_USER' => $user->getUsername(),
'PHP_AUTH_PW' => $user->getUsername(),
]
);
Next, a User not being refreshed took some more.
In config/packages/security.yaml, add the following:
security:
firewalls:
test:
security: ~
This is to create the "test" key, as creating that immediately in the next file will cause a permission denied error. In config/packages/test/security.yaml, create the following:
security:
providers:
test_user_provider:
id: App\Tests\Functional\Security\UserProvider
firewalls:
test:
http_basic:
provider: test_user_provider
This adds a custom UserProvider specifically for testing purposes (hence usage App\Tests\ namespace). You must register this service in your config/services_test.yaml:
services:
App\Tests\Functional\Security\:
resource: '../tests/Functional/Security'
Not sure you'll need it, but I added in config/packages/test/routing.yaml the following:
parameters:
protocol: http
As PhpUnit is testing via CLI, there by default is no secure connection, can vary by environment so see if you need it.
Lastly, config for test framework in config/packages/test/framework.yaml:
framework:
test: true
session:
storage_id: session.storage.mock_file
All of the above config (apart from the http bit) is to ensure that a custom UserProvider will be used to provider User objects during testing.
This might excessive for others, but our setup (legacy) has some custom work for providing Users for authentication (which seems very related but far out of my current issue scope).
Back on to the UserProvider, it's setup like so:
namespace App\Tests\Functional\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
/** #var UserRepository */
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function loadUserByUsername($username)
{
try {
return $this->userRepository->getByUsername($username);
} catch (UserNotFoundException $e) {
throw new UsernameNotFoundException("Username: $username unknown");
}
}
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return User::class === $class;
}
}
Note: should you use this, you need to have a getByUsername function in your UserRepository.
Please note, this might not be the solution for you. Maybe you need to change it up, maybe it's completely off. Either way, thought to leave a solution for any future souls.
I am starting to work with services in Symfony and therefore created the example service from the symfony documentation:
namespace AppBundle\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger){
}
public function getMessage()
{
$this->logger->info('Success!');
}
}
I call that service in my controller (I also have the use Statement:
: use AppBundle\Service\MessageGenerator;
$messageGenerator = $this->get(MessageGenerator::class);
$message = $messageGenerator->getMessage();
$this->addFlash('success', $message);
My service is defined in the services.yml file:
app.message_generator:
class: AppBundle\Service\MessageGenerator
public: true
so in my eyes I did everything exactly as described in the documentation and when calling:
php app/console debug:container app.message_generator
in my commandline I get my service:
Option Value
------------------ ------------------------------------
Service ID app.message_generator
Class AppBundle\Service\MessageGenerator
Tags -
Scope container
Public yes
Synthetic no
Lazy no
Synchronized no
Abstract no
Autowired no
Autowiring Types -
Now when I execute the controller function where I call my service I still get the error:
You have requested a non-existent service "appbundle\service\messagegenerator".
Any ideas?
Symfony is a bit confusing at naming: you retrieve the service by requesting it by its defined name: app.message_generator.
$messageGenerator = $this->get('app.message_generator');
Symfony has recently suggested switching from a give-name (app.message_generator) that you are defining the service as, to the class name (AppBundle\Service\MessageGenerator). They are both just 'a name' to call the service.
You are trying to use both, when only the given name is defined.
In the long term, it's suggested to use the ::class based name, and quite possibly allow the framework to find the classes itself, and configure them itself too. This means that, by default, all services are private, and are handled by the framework & it's service container.
In the meantime, while you are learning, you can either:
$messageGenerator = $this->get('app.message_generator');
or define explicitly define the service, and make it public, so it can be fetched with ->get(...) from the container.
# services.yml
AppBundle\Service\MessageGenerator:
class: AppBundle\Service\MessageGenerator
public: true
# php controller
$messageGenerator = $this->get(MessageGenerator::class);
or just injected automatically into the controller, when that is requested
public function __construct(LoggerInterface $logger, MessageGenerator $msgGen)
{
$this->messageGenerator = $msgGen;
}
public function getMessage()
{
$result = $this->messageGenerator->do_things(....);
$this->logger->info('Success!');
}
I used this version to implement the 2 bundles. However, I want the user to set a password for his account so after successful authentification and user creation, he'll be refirected to a form where he'll enter that. Since FOSUBUserProvider is a service, I was thinking to make another service that will handle the password form. I injected the second service into the first one but I need #templating which I've set up as a parameter but I have no idea how to take it and I'm getting a warning that he's missing. How do I solve this?
# /FOSUBUserProvider
$passwordSetter = Controller::get('register_social_password_picker');
I understand that it needs a second parameter(for templating), where do I take it from? Or am I approaching this the wrong way?
class RegisterPassword
{
protected $user;
protected $templating;
public function __construct($user, $templating)
{
$this->user = $user;
$this->templating = $templating;
}
...
}
services.yml
my_user_provider:
class: AppBundle\Security\Core\FOSUBUserProvider
arguments: [#fos_user.user_manager,{facebook: facebook_id, twitter: twitter_id, linkedin: linkedin_id}, "#register_password" ]
register_password:
class: AppBundle\Service\RegisterPassword
arguments: ["#templating" ]
LE:
services.yml
register_password:
class: AppBundle\Service\RegisterPassword
arguments: [ setMailer, ["#templating"] ]
class RegisterPassword
{
protected $user;
protected $templating;
public function __construct( )
{
}
public function setPassword(User $user, $templating)
{
$this->templating = $templating;
...
}
Your problem is that the service constructor is expecting two arguments ($user and $templating) and you are passing only one (#templating). These parameters are passed when the service is built, so my guess is that you do not want $user to be a parameter for the constructor (at this point you do not know which user is going to use the service), so just pass the $templating parameter and you can add the user later on with a different call.
Since this seems to generate a circular reference problem, you may use one of three different strategies:
Inject the full service container and get the templating service when you need it (not recommended)
Use setter injection (http://symfony.com/doc/current/components/dependency_injection/types.html)
Since this calls the setter method after the service is built my guess is that the circular reference will be avoided (not 100% sure)
If this does not work, create the setter method but do not call it at service construction point but rather call it later when you want to use the service
However, the approach I tried was a little off, the redirection after login can be easily set in security.yml>security>firewalls>firewall_name>default_target_path
Mine looks like:
default_target_path: /redirect
I am making a messenger which can send email messages or sms messages, and has the possibility to send them now or send them later (the information is saved in the DB). I've made 2 solutions, but neither is satisfying me.
I'm centralising the code in one Factory, and the code of the Factory pattern is very easy:
class MessageFactory
{
static public function get($type,$em)
{
$instance = null;
switch ($type) {
case 'email':
$instance = new EmailMessage($em);
break;
....
return $instance;
}
class EmailMessage implements MessangerInterface
{
...
public function send( $eMessage,array $receivers, $time=NULL)
{
interface MessangerInterface
{
public function send($message,array $receivers);
}
1st solution: Just call as an ordinary static method
$messanger = Factory\MessageFactory::get('email',$em);
$messanger->send($eMessage, array('tom'=>'tom#gmail.com'));
This is a bad solution, because I need to pass in a Doctrine Manager as a parameter to the method
2nd solution: To use it as a Symfony 2 Service
services:
my.messanger:
class: Bundle\Factory\MessangerInterface
factory_class: Bundle\Factory\MessageFactory
factory_method: get
arguments:
messanger_type: %messanger.type%
and also pass in Doctrine as an argument. But using such a solution I can't choose messanger.type in my code, it's defined using a configuration parameter as email or sms; I need to have the capability in code to choose the type.
Also I have a problem that inside the class I need to send email or sms, and that means that I need an external service, getting it like this:
class EmailMessage implements MessangerInterface
{
if ('AppCache' == get_class($kernel)) {
$kernel = $kernel->getKernel();
}
$kernel->getContainer()->get('mailer')->send($eMessage);
which seems like very bad practice.
Please, are you able to advise me on any better solutions?
I want to follow the "thin controller fat model" concept.
It seems like option 2, using Symfony 2 Services, would be best.
I considered suggesting that you let the Factory be the Service, and pass the type in to get the Messenger instance, rather than fixing it in config, but if what you want is to only have one of each type of Messenger then that's unhelpful (the Factory would keep creating more and more Messengers). So instead I think you need to define two Services, one for each Messenger.
And if you don't want to have to fetch another Service within your Messenger, you need to inject that in when you get the Messenger.
e.g.
services:
mailer:
class: Mailer
smser:
class: SMSer
email.messanger:
class: Bundle\Factory\MessangerInterface
factory_class: Bundle\Factory\MessageFactory
factory_method: get
arguments:
messanger_type: email
sender: #mailer
sms.messanger:
class: Bundle\Factory\MessangerInterface
factory_class: Bundle\Factory\MessageFactory
factory_method: get
arguments:
messanger_type: sms
sender: #smser
And your Factory needs to accept the new $sender argument:
class MessageFactory
{
static public function get($type,$em,$sender)
{
$instance = null;
switch ($type) {
case 'email':
$instance = new EmailMessage($em, $sender);
break;
....
return $instance;
}
interface MessangerInterface
{
public function send($message,$sender, array $receivers);
}
Then when you call it, you ask for either of the Messengers specifically:
$this->get('email.messenger')->send($emailMessage);
$this->get('sms.messenger')->send($smsMessage);