HWIOAuthBundle custom service provider arguments - symfony

I'm trying to set up HWIOAuthBundle to work with FOSUserBundle.
While making my own User Provider that extends FOSUBUserProvider, I did the following:
namespace Naroga\Reader\CommonBundle\Service\Security;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
class NarogaUserProvider extends FOSUBUserProvider {
public function loadUserByOAuthUserResponse(UserResponseInterface $response) {
[...]
}
}
My services.yml is as follows:
naroga.reader.common.security.user_provider:
class: Naroga\Reader\CommonBundle\Service\Security\NarogaUserProvider
arguments: [ #fos_user.user_manager ]
Whenever I run the program, I get the following error:
Argument 2 passed to HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider::__construct() must be of the type array, none given, called in
This makes great sense, because FOSUBUserProvider::__construct's signature is public function __construct(UserManagerInterface $userManager, array $properties).
I have no idea what to define as being my second parameter to my service so it can override FOSUBUserProvider. I've been googling it and all I find is people with the same question, no answers.
I'd be forever grateful to the gentle soul that tells me what the second parameter must be in order to comply with FOSUBUserProvider's signature.
Thank you.

The second argument is used for mapping.
As I understand, this is the name corresponding OAuth service and name of field in your entity.
For example, you can send them as follows:
naroga.reader.common.security.user_provider:
class: Naroga\Reader\CommonBundle\Service\Security\NarogaUserProvider
arguments: [ #fos_user.user_manager, { facebook: facebookId, twitter: twitterId } ]

In case you would like to pass also some additional arguments, let's say fire an event using dispatcher which was my case, just overwrite the constructor.
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
FOSUBUserProvider extends BaseClass
{
/**
* #var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* Constructor.
*
* #param UserManagerInterface $userManager FOSUB user provider.
* #param array $properties Property mapping.
* #param EventDispatcherInterface $eventDispatcher
*/
public function __construct(
UserManagerInterface $userManager,
array $properties,
EventDispatcherInterface $eventDispatcher)
{
$this->userManager = $userManager;
$this->properties = array_merge($this->properties, $properties);
$this->accessor = PropertyAccess::createPropertyAccessor();
$this->eventDispatcher = $eventDispatcher;
}
And the service definition:
tb_user_provider:
class: "%tb_user_provider.class%"
arguments: [#fos_user.user_manager, { facebook: facebook_id }, #event_dispatcher]

Related

Symfony upgrade give me error from 4.1 to 4.4

I just made the migration from symfony 4.1 to 4.4
I have this error:
Argument 1 passed to App\EventListener\KernelRequestListener::__construct() must be an instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage, instance of Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage given, called in C:\xampp\htdocs\chat-project-symfony\var\cache\dev\Container06Mjwya\srcApp_KernelDevDebugContainer.php on line 1130
While if you look at my KernelRequestListener :
<?php
namespace App\EventListener;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
//..
class KernelRequestListener
{
private $tokenStorage;
/**
* KernelRequestListener constructor.
* #param TokenStorage $tokenStorage
* ...
*/
public function __construct(TokenStorage $tokenStorage/*...*/)
{
$this->tokenStorage = $tokenStorage;
//..
}
}
Here is my config/services.yaml file:
#...
services:
#..
App\EventListener\KernelRequestListener:
arguments: [ '#security.token_storage' ]
tags:
- { name: kernel.event_listener, event: kernel.request }
- { name: kernel.event_listener, event: kernel.response }
I don't know why symfony tell me that I'm using Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage while it's clearing written Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage
I already tried to clear the cache folder and also delete the cache folder and it didn't change.
How can I fix this ?
Thank you
I don't know why symfony tell me that I'm using Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage while it's clearing written Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage
It's not symfony but PHP's type checking feature. You are stating that your Listener wants a TokenStorage but symfony is passing to it different class, thus the error.
So, as #JaredFarrish pointed, you should be using TokenStorageInterface in your constructor, like this:
namespace App\EventListener;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
//..
class KernelRequestListener
{
private $tokenStorage;
/**
* KernelRequestListener constructor.
* #param TokenStorageInterface $tokenStorage
* ...
*/
public function __construct(TokenStorageInterface $tokenStorage/*...*/)
{
$this->tokenStorage = $tokenStorage;
//..
}
}
It's a common practice to use interfaces where they exists, because this way you will loose coupling with other classes and provide a way to unit test your classes.
Take a look: https://github.com/symfony/security-bundle/blob/master/Resources/config/security.xml#L22 they switched class for #security.token_storage service, because of deprecation. But when you use an interface you don't care of anything underlying, you just know that you will have your methods because of interface contract.
I fixed it changing this line:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
With this one:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface as TokenStorage;

Symfony validation callback

I'm trying to validate my entity via static callback.
I was able to make it work following the Symfony guide but something isn't clear to me.
public static function validate($object, ExecutionContextInterface $context, $payload)
{
// somehow you have an array of "fake names"
$fakeNames = array(/* ... */);
// check if the name is actually a fake name
if (in_array($object->getFirstName(), $fakeNames)) {
$context->buildViolation('This name sounds totally fake!')
->atPath('firstName')
->addViolation()
;
}
}
It works fine when I populate my $fakeNames array but what if I want to make it "dynamic"? Let's say I want to pick that array from the parameters or from the database or wherever.
How am I supposed to pass stuff (eg. the container or entityManager) to this class from the moment that the constructor doesn't work and it has to be necessarily static?
Of course my approach may be completely wrong but I'm just using the symfony example and few other similar issues found on the internet that I'm trying to adapt to my case.
You can create a Constraint and Validator and register it as service so you can inject entityManager or anything you need, you can read more here:
https://symfony.com/doc/2.8/validation/custom_constraint.html
or if you are on symfony 3.3 it is already a service and you can just typehint it in your constructor:
https://symfony.com/doc/current/validation/custom_constraint.html
This is the solution I was able to find in the end.
It works smoothly and I hope it may be useful for someone else.
I've set the constraint on my validation.yml
User\UserBundle\Entity\Group:
constraints:
- User\UserBundle\Validator\Constraints\Roles\RolesConstraint: ~
Here is my RolesConstraint class
namespace User\UserBundle\Validator\Constraints\Roles;
use Symfony\Component\Validator\Constraint;
class RolesConstraint extends Constraint
{
/** #var string $message */
public $message = 'The role "{{ role }}" is not recognised.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
and here is my RolesConstraintValidator class
<?php
namespace User\UserBundle\Validator\Constraints\Roles;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RolesConstraintValidator extends ConstraintValidator
{
/** #var ContainerInterface */
private $containerInterface;
/**
* #param ContainerInterface $containerInterface
*/
public function __construct(ContainerInterface $containerInterface)
{
$this->containerInterface = $containerInterface;
}
/**
* #param \User\UserBundle\Entity\Group $object
* #param Constraint $constraint
*/
public function validate($object, Constraint $constraint)
{
if (!in_array($object->getRole(), $this->containerInterface->getParameter('roles'))) {
$this->context
->buildViolation($constraint->message)
->setParameter('{{ role }}', $object->getRole())
->addViolation();
}
}
}
Essentially, I set up a constraint which, every time a new user user is registered along with the role, that role must be among those set in the parameters. If not, it builds a violation.

Symfony Customrepository inject translator

I am not sure if this is even best practice or possible at all.
So I have a situation where I use DataTables and I need to change a boolean value to text in order to display true/false instead of numbers. But I also need to do that in different languages.
Since I need this in several places in the app i was thinking that I should make an app specific Repository class that extends EntityRepository and use it as extended class for the repositories I am building. For this i want to inject translator object in in order to translate some keys, but translation is never set:
CustomRepository class
class CustomRepository extends EntityRepository
{
/**
* #var Translator
*/
protected $translator;
/**
* #param Translator $translator
*/
public function setTranslator(Translator $translator)
{
$this->translator = $translator; //*******this one is not set...
}
/**
* Replace bool results into string values
*
* #param $aRes
* #param $sField
*
* #return mixed
*/
protected function _replaceBoolToStringResult(&$aRes, $sField)
{
if (1 == $aRes[$sField]) {
$aRes[$sField] = str_replace('1', $this->translator->trans('site.true'), $aRes[$sField]);
} else {
$aRes[$sField] = str_replace('0', $this->translator->trans('site.false'), $aRes[$sField]);
}
return $aRes;
}
}
services.yml
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
#should i call here all the constructor vars from EntityRepository class as arguments?
calls:
- [setTranslator, ["#translator.default"]]
Repository with custom DQL
class SettingsRepository extends CustomRepository
{
public function findOverviewSettingsAsJson()
{
$aResult = $this->createQueryBuilder('s')
->select('s.identifier, s.type, s.isActive')
->getQuery()
->getScalarResult();
// ******** HERE I WANT TO USE _replaceBoolToStringResult
return json_encode($aResult);
}
}
I found this article by Matthias to be useful on this issue. (I know link only answers are frowned on...)
You must use the factory pattern when you use a repository as a service.
See possible duplicates :
Symfony 2: Creating a service from a Repository
How to inject a repository into a service in Symfony2?
Note : the syntax changed in latest SF version : http://symfony.com/doc/current/components/dependency_injection/factories.html
Edit :
You should use your repository as a service :
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments:
- App\CommonBundle\Entity\CustomEntity
calls:
- [setTranslator, ["#translator.default"]]
Then call this service as any other service in your code. For example from inside a controller :
$this->get('app.custom.repository')->...

Symfony2/Doctrine: Get the field(s) that changed after "Loggable" entity changed

In a Symfony2 project I'm using the Loggable Doctrine Extension.
I saw that there is a LoggableListener.
Is there indeed an event that gets fired when a (loggable) field in a loggable entity changes? If it is so, is there a way to get the list of fields that triggered it?
I'm imagining the case of an entity with, let's say 10 fields of which 3 loggable. For each of the 3 I want to perform some actions if they change value, so 3 actions will be performed if the 3 of them change.
Any idea?
Thank you!
EDIT
After reading the comment below and reading the docs on doctrine's events I understood have 3 options:
1) using lifecycle callbacks directly at the entity level even with arguments if I'm using doctrine >2.4
2) I can listen and subscribe to Lifecycle Events, but in this case the docs say that "Lifecycle events are triggered for all entities. It is the responsibility of the listeners and subscribers to check if the entity is of a type it wants to handle."
3) doing what you suggest, which is using an Entity listener, where you can define at the entity level which is the listener that is going to be "attached" to the class.
Even if the first solution seems easier, I read that "You could also use this listener to implement validation of all the fields that have changed. This is more efficient than using a lifecycle callback when there are expensive validations to call". What's considered an "expensive validation?".
In my case what I have to perform is something like "if field X of entity Y changed than add a notification on the notification table saying "user Z changed the value of X(Y) from A to B"
Which would be the most suitable approach, considering that I have around 1000 fields like those?
EDIT2
To solve my problem I'm trying to inject the service_container service inside the listener, so that I can have access to the dispatcher to dispatch a new event which can perform the persist of new entity I need. But how can I do that?
I tried the usual way, I add the following to the service.yml
app_bundle.project_tolereances_listener:
class: AppBundle\EventListener\ProjectTolerancesListener
arguments: [#service_container]
and of course I added the following to the listener:
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
but I get the following:
Catchable Fatal Error: Argument 1 passed to AppBundle\ProjectEntityListener\ProjectTolerancesListener::__construct() must be an instance of AppBundle\ProjectEntityListener\ContainerInterface, none given, called in D:\provarepos\user\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\DefaultEntityListenerResolver.php on line 73 and defined
Any idea?
The Loggable listener only saves the changesvalue for the watched properties of your entities over time.
It does not fire an event, it listens to the onFlush and postPersist doctrine events.
I think you are looking for Doctrine listeners on preUpdate and prePersist events where you can manipulate the changeset before a flush.
see: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html
If you are using Doctrine 2.4+ you can add them easily to your entity:
Simple entity class:
namespace Your\Namespace\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\EntityListeners({"Your\Namespace\Listener\DogListener"})
*/
class Dog
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="integer")
*/
private $age;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return int
*/
public function getAge()
{
return $this->age;
}
/**
* #param int $age
*/
public function setAge($age)
{
$this->age = $age;
}
}
Then in Your\Namespace\Listener you create the ListenerClass DogListener:
namespace Your\Namespace\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Your\Namespace\Entity\Dog;
class DogListener
{
public function preUpdate(Dog $dog, PreUpdateEventArgs $event)
{
if ($event->hasChangedField('name')) {
$updatedName = $event->getNewValue('name'). ' the dog';
$dog->setName($updatedName);
}
if ($event->hasChangedField('age')) {
$updatedAge = $event->getNewValue('age') % 2;
$dog->setAge($updatedAge);
}
}
public function prePersist(Dog $dog, LifecycleEventArgs $event)
{
//
}
}
Clear the cache and the listener should be called when flushing.
Update
You are right about recomputeSingleEntityChangeSet which was not needed in this case. I updated the code of the listener.
The problem with the first choice (in-entity methods) is that you can't inject other services in the method.
If you only need the EntityManager then yes, it is the easiest way code-wise.
With an external Listener class, you can do so.
If those 1000 fields are in several separate entities, the second type of Listener would be the most suited. You could create a NotifyOnXUpdateListener that would contain all your watch/notification logic.
Update 2
To inject services in an EntityListener declare the Listener as a service tagged with doctrine.orm.entity_listener and inject what you need.
<service id="app.entity_listener.your_service" class="Your\Namespace\Listener\SomeEntityListener">
<argument type="service" id="logger" />
<argument type="service" id="event_dispatcher" />
<tag name="doctrine.orm.entity_listener" />
</service>
and the listener will look like:
class SomeEntityListener
{
private $logger;
private $dispatcher;
public function __construct(LoggerInterface $logger, EventDispatcherInterface $dispatcher)
{
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
public function preUpdate(Block $block, PreUpdateEventArgs $event)
{
//
}
}
According to: How to use Doctrine Entity Listener with Symfony 2.4? it requires DoctrineBundle 1.3+

Lookup route in symfony 2

I've defined a route in my app routing file:
RouteName:
pattern: /some/route
defaults: { _controller: MyAppBundle:Controller:action }
In a controller I can use:
$this->get('router')->generate('RouteName');
How would I simply access that from a fresh class I create, for example a view class that doesn't extend anything:
namespace My\AppBundle\View;
class ViewClass {
public function uri()
{
return getTheRoute('RouteName');
}
}
You need to inject "router" service into your ViewClass. Eg. in place where your define your ViewClass service:
viewclass.service:
class: Namespace\For\ViewClass
arguments:
router: "#router"
and then in your constructor:
public function __construct(\Symfony\Bundle\FrameworkBundle\Routing\Router $router)
{
$this->router = $router;
}
The clue is in how the $this->generateUrl() method works in Controllers. See:
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param Boolean $absolute Whether to generate an absolute URL
*
* #return string The generated URL
*/
public function generateUrl($route, $parameters = array(), $absolute = false)
{
return $this->container->get('router')->generate($route, $parameters, $absolute);
}
So you'll need to define your class as a service and inject the #router service. Either that or have your class implement ContainerAwareInterface, but the first method would definitely be better.
You should register your class as a service and insert the router as a dependency.
See the chapter on the service container in the excellent symfony2 docs.
If you're not familiar with the concepts of the service container and dependency injection, you might feel a bit overwhelmed. However, try your best to understand it because it is a essential part of the symfony2 architecture.
You could pass the entire container from your controller to your view class on instantiation. This is NOT BEST PRACTICE and not recommended.
class View
{
protected $container;
public function __construct(\Symfony\Component\DependencyInjection\Container $container)
{
$this->container = $container;
}
}
Then in your code you could use
$this->container->get('router')->generate($route, $parameters, $absolute);

Resources