Linking a user to another entity on create in Symfony2 - php

I am using the FOS_user bundle and when I create a user I want to link it to another Entity which is Company. I have tried to create a Listener using the following code:
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event) {
}
This is working good and I have access to $event in the function. Now, I want to access the name of the company that the user added in the form. Also, I want to create a company and bind it to the new user, so something like this:
$company = new Entity\Company();
$company->setTitle($theInputFromTheForm);
$user->addCompany($company);
I don't know how to access the data of the form and save the company in the user. Is using a listener the proper way to do it?

The event FOSUserEvents::REGISTRATION_SUCCESS receive a FormEvent object. You can get the registration form in your listener using $event->getForm()
So in you case;
public function onRegistrationSuccess(FormEvent $event) {
$registrationForm = $event->getForm();
$registrationFormData = registrationForm->getData();
}

I ended up creating a form type CompanyType that adds the title of my company. In my RegistrationFormType of my UserBundle I added a collection to this form type. I created a listener on FOSUserEvents::REGISTRATION_INITIALIZE to add an empty company.
public function onRegistrationInitialize(UserEvent $event) {
$user = $event->getUser();
$user->addCompany($company);
}

Related

How to register a default login user?

**Context: ** I have 2 associated entities, being "persona" and "ingreso".
I tried to capture the logged in user and send it as a default variable in the form:
TextField::new('person','Person')
->formatValue(function ($value) {
return $value = $this->getUser();
})
->hideOnForm()
But: This arrives as a Null value in the database.
This is why I try to capture the user and save it from the entity, but I don't know a correct way to do it.
You are using ->hideOnForm, which remove the field in your form, so nothing is sent concerning person.
There is multiple way to do what you want, including similar answer such has having a hidden select with your user, however I do not consider it a good solution.
Have you considered using Event ?
In your case you could either listen using Doctrine events or with EasyAdmin Events.
Symfony events
<?php
namespace App\EventSubscriber;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
//... other imports
class EasyAdminSubscriber implements EventSubscriberInterface
{
private $tokenStorage
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage
}
public static function getSubscribedEvents()
{
return [BeforeEntityUpdatedEvent => ['beforeEntityUpdatedEvent'], ];
}
public function beforeEntityUpdatedEvent(BeforeEntityUpdatedEvent $event)
{
$entity = $event->getEntityInstance();
if ($entity instanceof YourEntityYouWantToListenTo) {
$entity->setPerson($this->tokenStorage->getToken()->getUser());
}
}

How to access the current user within a Notification?

On the Laravel docs, it states:
Using The Notification Facade
Alternatively, you may send
notifications via the Notification facade. This is useful primarily
when you need to send a notification to multiple notifiable entities
such as a collection of users. To send notifications using the facade,
pass all of the notifiable entities and the notification instance to
the send method:
Notification::send($users, new InvoicePaid($invoice));
So I am doing this within my controller:
public function index()
{
$subscribers = Subscribers::all();
Notification::send($subscribers, new NewVacancy($subscribers));
}
And here is my Notification class
class NewVacancy extends Notification implements ShouldQueue
{
use Queueable;
public $subscriber;
public function __construct( $subscribers)
{
$this->subscriber = $subscribers;
}
public function toMail($notifiable)
{
return (new MailMessage)->view(
'mail.new-vacancy',
['uuid' => $this->subscriber->uuid]// This fails as $subscriber is a collection
);
}
....
The problem is that within the NewVacancy class, the $subscriber that is passed in is a full collection of all subscribers and not the individual notification being sent.
Now I know I could do a loop over $subscribers and fire the Notification::send() each time but that defeats the point of using facade to begin with.
The general goal is to send emails to all $subscribers with the ability to pass in unique subscriber data using a blade template.
I found out you can access the current user via the $notifiable entity thats passed into the toMail() method.
public function toMail($notifiable)
{
return (new MailMessage)->view(
'mail.new-vacancy',
['uuid' => $notifiable->uuid]
);
}
Please note that $notifiable represents the user object which is getting notified.
$user_id = $notifiable->id;

Decoupled way of modifying forms in Symfony 3/4

I'd like to be able to alter a form using the Symfony Event Dispatcher. The Symfony documentation talks about dynamic form modification, however, in all examples the event listener or subscriber is created in the form class. I'd like to keep this logic decoupled from my form class.
How can I modify a Symfony form without having to specify which event listeners are going to be called in the form class?
Probably what you need is a form type extension, it allow you to modify any existing form types across the entire system. There, you can add event listeners/subscribers or what you want to any specific or generic form type.
However, this task tends to get tedious if it's a very frequent case. So doing something like this can provide you with a perfect fit:
class FoobarFormSubscriber implements EventSubscriberInterface, FormEventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public static function getFormClass()
{
return FoobarType::class;
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$form->add('custom', null, array('mapped' => false));
}
}
But obviously this isn't a feature implemented by Symfony. Here I leave you a recipe to achieve it:
First, create a new form type extension to add the subscriber to the form builder according to configuration:
class FormEventTypeExtension extends AbstractTypeExtension
{
private $subscribers;
public function __construct(array $subscribers = array())
{
$this->subscribers = $subscribers;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$formClass = get_class($builder->getType()->getInnerType());
if (isset($this->subscribers[$formClass])) {
foreach ($this->subscribers[$formClass] as $subscriber) {
$builder->addEventSubscriber($subscriber);
}
}
}
public function getExtendedType()
{
return FormType::class;
}
}
Create a new interface to configure the form class to listen to:
interface FormEventSubscriberInterface
{
public static function getFormClass();
}
Finally, into a new compiler pass, injects to the extension service all registered kernel.event_subscriber that implement the previous interface:
public function process(ContainerBuilder $container)
{
$subscribers = array();
foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $serviceId => $tags) {
$subscriberClass = $container->getDefinition($serviceId)->getClass();
if (is_subclass_of($subscriberClass, FormEventSubscriberInterface::class, true)) {
$subscribers[$subscriberClass::getFormClass()][] = new Reference($serviceId);
}
}
$extensionDef = $container->getDefinition(FormEventTypeExtension::class);
$extensionDef->setArgument(0, $subscribers);
}
Then, your custom subscribers are decoupled and ready to work as is, just make sure to implement both interfaces (EventSubscriberInterface, FormEventSubscriberInterface) and register the event subscriber as service.

Symfony Restful API - Expose virtual property isLiked by current logged in user

There are two entities Restaurant and Users. Restaurant entity has many-to-many relation with user, field name favoriteBy.
<many-to-many field="favoriteBy" target-entity="UserBundle\Entity\Users" mapped-by="favoriteRestaurants"/>
I am using JMS Serializer along with FOSRestfulAPI. In restaurant listing API I have to expose one extra boolean field "isFavorited", which will be true if current logged in user has in array collection favoriteBy.
How I can find whether current user has favorited the restaurant or not within entity?
/**
* Get is favorited
* #JMS\VirtualProperty()
* #JMS\Groups({"listing", "details"})
*/
public function isFavorited()
{
// some logic in entity
return false;
}
One way I am thinking is to inject current user object to entity and user contains method to find out, but its look like not good approach.
Please suggest me some method, or guide me to right direction.
You could implments an EventSubscriberInterface as described here in the doc.
As Example:
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
...
class RestaurantSerializerSubscriber implements EventSubscriberInterface
{
protected $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents()
{
return [
[
'event' => 'serializer.post_serialize',
'class' => Restaurant::class,
'method' => 'onPostSerialize',
],
];
}
public function onPostSerialize(ObjectEvent $event)
{
$visitor = $event->getVisitor();
$restaurant = $event->getObject();
// your custom logic
$isFavourite = $this->getCurrentUser()->isFavourite($restaurant);
$visitor->addData('isFavorited', $isFavourite);
}
/**
* Return the logged user.
*
* #return User
*/
protected function getCurrentUser()
{
return $this->tokenStorage->getToken()->getUser();
}
And register, as YML example:
acme.restaurant_serializer_subscriber:
class: Acme\DemoBundle\Subscriber\RestaurantSerializerSubscriber
arguments: ["#security.token_storage"]
tags:
- { name: "jms_serializer.event_subscriber" }
Hope this help
PS: You could also intercept the serialization group selected, let me know if you neet that code.
Entity should know nothing about current logged in user so injecting user into entity is not a good idea.
Solution 1:
This can be done with custom serialization:
// serialize single entity or collection
$data = $this->serializer->serialize($restaurant);
// extra logic
$data['is_favourited'] = // logic to check if it's favourited by current user
// return serialized data
Solution 2
This can be also achieved by adding Doctrine2->postLoad listener or subscriber after loading Restaurant entity. You can add dependency for current authenticated token to such listener and set there Restaurant->is_favorited virtual property that will be next serialized with JMS.

How to add model events to an event subscriber class in Laravel 5.1

I would like to refactor some event so I've created an event subscriber class.
class UserEventListener
{
public function onUserLogin($event, $remember) {
$event->user->last_login_at = Carbon::now();
$event->user->save();
}
public function onUserCreating($event) {
$event->user->token = str_random(30);
}
public function subscribe($events)
{
$events->listen(
'auth.login',
'App\Listeners\UserEventListener#onUserLogin'
);
$events->listen(
'user.creating',
'App\Listeners\UserEventListener#onUserCreating'
);
}
}
I register the listener as follows:
protected $subscribe = [
'App\Listeners\UserEventListener',
];
I added the following to the boot method of the user model as follows:
public static function boot()
{
parent::boot();
static::creating(function ($user) {
Event::fire('user.creating', $user);
});
}
But when I try the login I get following error:
Indirect modification of overloaded property App\User::$user has no effect
Whats wrong with the onUserLogin signature? I thought you can access user using $event->user...
If you want to use event subscribers, you'll need to listen on the events that Eloquent models fire at different stages of their lifecycle.
If you have a look at fireModelEvent method of Eloquent model, you'll see that the event names that are fired are built the following way:
$event = "eloquent.{$event}: ".get_class($this);
where $this is the model object and $event is the event name (creating, created, saving, saved, etc.). This event is fired with a single argument, being the model object.
Another option would be to use model observers - I prefer that to event subscribers as it makes listening on different lifecycle events easier - you can find an example here: http://laravel.com/docs/4.2/eloquent#model-observers
Regarding auth.login, when this event is fired, 2 parameters are passed along - user that logged in and the remember flag. Therefore, you'll need to define a listener that takes 2 arguments - first will be the user, second will be the remember flag.

Categories