Modifying User entity when registering using FOS User Bundle and Symfony2 - events

As many people know, the FOS User Bundle doesn't provide roles automatically when a user registers. The most common solution is to either a) modify the User entity constructor to automatically assign a role, or b) override the entire registration controller.
Neither of these solutions seems perfect, and I want to make use of the Events that the FOS user bundle provides.
I have managed to capture the event I want (FOSUserEvents::REGISTRATION_INITIALIZE), but I am having trouble figuring out how to pass the modified User entity back to the registration form.
The code I have so far is as follows:
namespace HCLabs\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\Model\UserInterface;
class AutoRoleAssignmentListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [ FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInitialise' ];
}
public function onRegistrationInitialise( UserEvent $event )
{
$user = $event->getUser();
$user->addRole( 'ROLE_USER' );
// what do
}
}
The YML for the event listener:
services:
hc_labs_user.reg_init:
class: HCLabs\UserBundle\EventListener\AutoRoleAssignmentListener
tags:
- { name: kernel.event_subscriber }
If more code is needed I'm happy to provide it. Thanks for your help.

Answer is very simple - you have to do nothing to get updated User object in registration form after updated User in event listener for FOSUserEvents::REGISTRATION_INITIALIZE event.
Let me explain. FOSUserEvents::REGISTRATION_INITIALIZE is dispatched in RegistrationController by:
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, new UserEvent($user, $request));
And, before this dispatch in code (https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Controller/RegistrationController.php#L43) new User is created:
$user = $userManager->createUser();
$user->setEnabled(true);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, new UserEvent($user, $request));
During dispatching, by default PHP call_user_func (http://php.net/manual/en/function.call-user-func.php ) is called with pasted event name (function in defined object) and Event object. After that, event listener has possibility to modify pasted Event object - particularly event property.
In your case, your event listener modify User property via:
$user = $event->getUser();
$user->addRole( 'ROLE_USER' );
So in fact, you have to do nothing to pass the modified User entity back to the registration form.

Related

Laravel Nova Observe not connecting to tenant database

I have a multi tenant App. My system database I have models- User, Billing, FrontEnd ... and using policies I'm able to show, hide and prevent viewing and actions by tenant.
Each tenant has a database with models- Member, Event, Item ...
I set each model database based on the Auth::user()->dbname in the _construct method. This allows me to set my dbname to a clients database for tech support.
class Item extendsw Model {
public function __construct() {
parent::__construct();
if(Auth::user()->dbname) {
Config::set('database.connections.tenant.database', auth()->user()->dbname);
$this->connection = 'tenant';
}
}
This all works as planned until I add and Observer for a client model e.g. Member
I now get an error on any Observer call.
Trying to get property on non object Auth::user()->dbname.
Where should I be registering the Observer? I tried AppServiceProvider and NovaServiceProvider.
I think that happens because the observer instantiates your User model before the request cycle has started and that means that your User instance does not exist yet neither has been bound in the Auth facade.
Thus, Auth::user() returns null and you are trying to get a property from it.
A way to solve the issue may be to check if the user instance exists or not:
public function __construct() {
parent::__construct();
if (optional(Auth::user())->dbname !== null) {
Config::set('database.connections.tenant.database', auth()->user()->dbname);
$this->connection = 'tenant';
}
}
The optional helper return the value of the accessed property (dbname in your case) if and only if the argument is not null, otherwise the whole call will return a null value instead throwing an exception.
If that is not the case, maybe update the question with the error stacktrack and the code/action that triggers the error

Is there a way to send params to an observer?

Is there a way to send parameters to an Observer in Eloquent ORM?
Based on laravel's documentation:
User::observe(UserObserver::class);
observe method receive a class, not an instance of an object. So I cant do something like:
$observer = new MyComplexUserObserver($serviceA, $serviceB)
User::observe($observer);
So, in my code I can do something like:
class MyComplexUserObserver
{
private $serviceA;
private $serviceB;
public function __constructor($serviceA, $serviceB){
$this->serviceA = $serviceA;
$this->serviceB = $serviceB;
}
public function created(User $user)
{
//Use parameters and services here, for example:
$this->serviceA->sendEmail($user);
}
}
Is there a way to pass parameters or services to a model observer?
Im not using laravel directly, but i'm using eloquent (illuminate/database and illuminate/events)
Im not trying to send additional parameters to an explicit event like in: Laravel Observers - Any way to pass additional arguments?, i'm trying to construct an observer with additional parameters.
FULL SOLUTION:
Thank you to #martin-henriksen.
use Illuminate\Container\Container as IlluminateContainer;
$illuminateContainer = new IlluminateContainer();
$illuminateContainer->bind(UserObserver::class, function () use ($container) {
//$container is my project container
return new UserObserver($container->serviceA, $container->serviceB);
});
$dispatcher = new Dispatcher($illuminateContainer);
Model::setEventDispatcher($dispatcher); //Set eventDispatcher for all models (All models extends this base model)
User::observe(UserObserver::class);
In the Illuminate events there is the line, this indicates on event subscription it utilities the container. This mean we can use this to our advantage, i'm not super familiar with non Laravel bootstrapped applications. But where ever your app is defined, you will bind your class to your own class.
$container = new Container();
$container->bind(MyComplexUserObserver::class, function ($app) {
return new MyComplexUserObserver($serviceA, $serviceB, $theAnswerToLife);
});
$dispatcher = new Dispatcher($container);
This will result in, next time your application resolves your class, it will use this version of it and therefor you can setup your class as you intend.
Edit: an example how you can utilize the Laravel container, to utilize the bind functionality.

Yii2 session event before close/destroy

I want to run some code every time before user session is being destroyed for any reason. I haven't found any events binded to session in official documentation. Has anyone found a workaround about this?
There are no events out of the box for Session component.
You can solve this problem with overriding core yii\web\Session component.
1) Override yii\web\Session component:
<?php
namespace app\components;
use yii\web\Session as BaseSession
class Session extends BaseSession
{
/**
* Event name for close event
*/
const EVENT_CLOSE = 'close';
/**
* #inheritdoc
*/
public function close()
{
$this->trigger(self::EVENT_CLOSE); // Triggering our custom event first;
parent::close(); // Calling parent implementation
}
}
2) Apply your custom component to application config:
'session' => [
'class' => 'app\components\Session' // Passing our custom component instead of core one
],
3) Attach handler with one of available methods:
use app\components\Session;
use yii\base\Event;
Event::on(Session::className(), Session::EVENT_OPEN, function ($event) {
// Insert your event processing code here
});
Alternatively you can specify handler as method of some class, check official docs.
As an alternative to this approach, take a look at this extension. I personally didn't test it. The Yii way to do it I think will be overriding with adding and triggering custom events as I described above.

Overriding the login() method of Sentry 2 package in Laravel 4

My basic aim is to extend the package class and override a method in it.
I have used Fnatte's ans as a reference : How to extend laravel 4 core?
Sentry2 is a package that i am using for authentication in larval 4.
A user can be logged in using Sentry::login($credentials)
I want to override the login method of the Sentry package and remove the check for activating the user(i have commented it in the code below)
public function login(UserInterface $user, $remember = false)
{
#prevent throwing error if not activated !
// if ( ! $user->isActivated())
// {
// $login = $user->getLogin();
// throw new UserNotActivatedException("Cannot login user [$login] as they are not activated.");
// }
$this->user = $user;
// Create an array of data to persist to the session and / or cookie
$toPersist = array($user->getId(), $user->getPersistCode());
// Set sessions
$this->session->put($toPersist);
if ($remember)
{
$this->cookie->forever($toPersist);
}
// The user model can attach any handlers
// to the "recordLogin" event.
$user->recordLogin();
}
Steps i have done till now :
1. Created a app/lib folder and added my extension class CustomSentry in it.
2. Added the app/lib folder to composer.json class map
app/lib/CustomSentry.php :
use Cartalyst\Sentry\Sentry;
use Cartalyst\Sentry\Users\UserInterface;
class CustomSentry extends Sentry{
public function login(UserInterface $user, $remember = false){
$this->user = $user;
// Create an array of data to persist to the session and / or cookie
$toPersist = array($user->getId(), $user->getPersistCode());
// Set sessions
$this->session->put($toPersist);
if ($remember)
{
$this->cookie->forever($toPersist);
}
// The user model can attach any handlers
// to the "recordLogin" event.
$user->recordLogin();
}
}
4. Created a service provider app/lib/CustomSentryServiceProvider
use Cartalyst\Sentry\SentryServiceProvider
class CustomSentryServiceProvider extends SentryServiceProvider{
//What should i put it here?
}
5. Register the service provider in app/config/app.php
'CustomSentryServiceProvider'
6. Use it as :
Sentry::login($credentials);
Well i figured out the answer to the question.
The better way to extend the class would be to use the same name. Since i am already using namespacing it would help reduce the confusion.
Assuming my apps name is MyApp i will only need to replace:
Cartalyst\Sentry
by:
MyApp\Cartalyst\Sentry
the rest of the SentryService provider can be copied as it is.
Then i can call the login method the normal way
Sentry::login($credentials)
I have doubts, because you have to copy whole package/Sentry. Did you tried to extend the Sentry class? This this the best way to do what you want to achieve.

symfony2 my own event

I made the authorization and authentication via facebook like here:
http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
and it works
Now I want to make my own event, this event will do something when the user authenticates using facebook. For example-will redirect the user to the home page.
I did it like this
http://symfony.com/doc/current/components/event_dispatcher/introduction.html
So I have this class
http://pastebin.com/2FTndtL4
I do not know how to implement it, what am I supposed to pass as an argument to the constructor
It's really simple. Symfony 2 event system is powerful, and service tags will do the job.
Inject the dispatcher into the class where you want to fire the event. The service id is event_dispatcher;
Fire the event with $this->dispatcher->dispatch('facebook.post_auth', new FilterFacebookEvent($args)) when needed;
Make a service that implements EventSubscriberInterface, defining a static getSubscribedEvents() method. Of course you want to listen to facebook.post_auth event.
So your static method will look like:
static public function getSubscribedEvents()
{
return array(
'facebook.post_auth' => 'onPostAuthentication'
);
}
public function onPostAuthentication(FilterFacebookEvent $event)
{
// Do something, get the event args, etc
}
Finally register this service as a subscriber for the dispatcher: give it a tag (eg. facebook.event_subscriber), then make a RegisterFacebookEventsSubscribersPass (see this tutorial). You compiler pass should retrieve all tagged services and inside the loop should call:
$dispatcher = $container->getDefinition('event_dispatcher');
$subscribers = $container->findTaggedServiceIds('facebook.event_subscriber');
foreach($subscribers as $id => $attributes) {
$definition->addMethodCall('addSubscriber', array(new Reference($id)));
}
This way you can quick make a subscriber (for logging, for example) simply tagging your service.
Event object is just some kind of state/data storage. It keeps data that can be useful for dispatching some kind of events via Subscribers and/or Listeners. So, for example, if you wanna pass facebook id to your Listener(s) - Event is the right way of storing it. Also event is the return value of dispatcher. If you want to return some data from your Listener/Subscriber - you can also store it in Event object.

Resources