I have an Event Subscriber with function setAcademicCalendar. I want to catch exceptions, display an error message in the flash bag and terminate form submit. Basically, I want to stay in the form (no redirects), give the user an error message and don't save the form.
I have two problems. 1. I don't know to terminate the process 2. The flash message is only displayed after a page refresh.
private function setAcademicCalendar(FormEvent $event) {
/** #var CalendarEvent $calendar_event */
$calendar_event = $event->getData();
if ($calendar_event->getCalendar() instanceof Calendar) {
try {
$sem = $this
->container
->get('academic_calendar')
->getSemester($calendar_event->getStart());
$calendar_event->setSemester($sem);
} catch (\Exception $e) {
$this->container->get('session')->getFlashBag()->add('error', $e->getMessage());
}
}
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::SUBMIT => 'submitData',
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
/**
* #param FormEvent $event
*/
public function submitData(FormEvent $event)
{
$this->setAcademicCalendar($event);
}
If you want to do this without redirection / refresh forget about validation in strict php. Only some strict frontend techs - like angular js , vue.js with some api calls
Related
I'm learnig the event Subscriber on Symfony, my idea is when I create a new blog post, I want to send a notification. I'm already able to get the last entity created.
I've added to code to get the Symfony notifier and be able to send a notification on Disocrd. I currently have the following code :
class EasyAdminSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
AfterEntityPersistedEvent::class => ['setBlogPostSlug'],
];
}
/**
* #param AfterEntityPersistedEvent $event
* #param ChatterInterface $chatter
* #return void
* #throws \Symfony\Component\Notifier\Exception\TransportExceptionInterface
*/
public function setBlogPostSlug(AfterEntityPersistedEvent $event)
{
$chatter = new Chatter();
$entity = $event->getEntityInstance();
if (!($entity instanceof Blog)) {
return;
}
$message = (new ChatMessage('You got a new invoice for 15 EUR.'));
// if not set explicitly, the message is send to the
// default transport (the first one configured)
$chatter->send($message);
}
}
But when I run the code I have the followinf error :
App\EventSubscriber\EasyAdminSubscriber::setBlogPostSlug(): Argument #2 ($chatter) must be of type Symfony\Component\Notifier\ChatterInterface, string given
I don't understand what's wrong. Do you any idea why ? And how to make things work ?
I'm trying to get all the errors from all the queries I do in my project and redirect this errors to a controller called "error" that will treat theses errors as I want. The problem looks like when I redirect all the information goes in the url generated by the function via GET.
I suppose that if this information is sent via POST will disappear this problem but I'm not using obviously any form inside the controller. So, how can I say to the redirect function that these information shouldn't go with the url and instead should go via POST?
Is possible what I'm trying to do?
Inside Controllers:
try {
$results = $queries->aQuery();
} catch (ErrorException $errorException) {
return $this->redirect($errorException->redirectResponse);
}
Inside the service query:
public function aQuery(){
$query="SELECT * FROM blabla ...";
try {
$stmt = $this->DB->->prepararQuery($query);
$stmt->execute();
$results = $stmt->fetchAll();
} catch (DBALException $DBALException) {
$errorException = new ErrorException($this->router->generate('error',
[
'errorQuery' => $query,
'errorData' => "0 => '".$data1."', 1 ....",
'errorOrigin' => 'a place',
'errorResponseText' => $DBALException->getMessage()
]
));
throw $errorException;
}
}
The ErrorException:
class ErrorException extends \Exception
{
/**
* #var \Symfony\Component\HttpFoundation\RedirectResponse
*/
public $redirectResponse;
/**
* ErrorException constructor.
* #param \Symfony\Component\HttpFoundation\RedirectResponse $redirectResponse
*/
public function __construct(string $redirectResponse)
{
$this->redirectResponse = $redirectResponse;
}
}
If what you are trying to achieve is a centralized way to handle exceptions have a look at https://symfony.com/doc/4.0/event_dispatcher.html#creating-an-event-listener and use kernel.exception event
public function onKernelException(GetResponseForExceptionEvent $event)
{
if (! $event->getException() instanceof ErrorException) {
return;
}
// handle your custom ErrorException
$response = new Response();
$response->setContent($event->getException()->getMessage());
// sends the modified response object to the event
$event->setResponse($response);
}
I would like to add one action to a page (let's say the route entity.node.canonical) but this action would appear and disappear from the page time to time.
So what I'm trying to do is to create the action using a deriver (I put my condition to show the action in the method getDerivativeDefinitions) and then, on a kernel event, I refresh the local actions using \Drupal::service('plugin.manager.menu.local_action')->clearCachedDefinitions();
But it still doesn't work !
So is anybody capable of showing me a method to show and hide an action ?
Here is my derivative:
class ConditionalAction extends DeriverBase implements ContainerDeriverInterface {
/**
* {#inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition)
{
// If the seconds is more than 30, hide the action.
if (date('s') > 30) {
return $this->derivatives;
}
// Else, show the action.
$menu_entry = $base_plugin_definition;
$menu_entry['route_name'] = 'my.route.name';
$menu_entry['title'] = 'Test action';
$menu_entry['appears_on'][] = \Drupal::routeMatch()->getRouteName();
$menu_entry['route_parameters'] = ['time' => date('s')];
$this->derivatives['my.action.name'] = $menu_entry;
return $this->derivatives;
}
}
And here is the event subscriber:
class MyModuleKernelSubscriber implements EventSubscriberInterface {
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => 'onKernelRequest'
];
}
/**
* Event called when a request is sent.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event) {
// Do not consider the ajax requests.
$request = $event->getRequest();
if ($request->isXmlHttpRequest() === TRUE) {
return;
}
// Flush local action cache.
\Drupal::service('plugin.manager.menu.local_action')->clearCachedDefinitions();
}
}
So, here, on each request, the local action should be cleared and the code should go through my derivative. But it's not working... Any thought ?
I really need it to be an action ! And I really need the URL argument to be dynamic as well (here, $menu_entry['route_parameters'] = ['time' => date('s')];).
Thank you
By looking inside the console command drupal cache:reset, I found that the lines
$bins = Cache::getBins();
$bins['render']->deleteAll();
$bins['discovery']->deleteAll();
reset the local actions on each requests.
So I have my answer now.
Hope this will help !
I have a problem regarding a Symfony application, I want to take as input the "username" or "id" for my controller , and receive information that is in my table "user" and also 2 other table for example : A user has one or more levels , and also it has points must earn points to unlock a level , I want my dan Home page display the username and the level and extent that it has , I jn am beginner and not come to understand the books symfony that I use, I work with PARALLEL " symfony_book " and " symfony_cook_book " and also tutorial youtube May I blocks , here is the code for my cotroler
"
/**
* #Route("/{id}")
* #Template()
* #param $id=0
* #return array
*/
public function getUserAction($id)
{
$username = $this->getDoctrine()
->getRepository('voltaireGeneralBundle:FosUser')
->find($id);
if (!$username) {
throw $this->createNotFoundException('No user found for id '.$id);
}
//return ['id' => $id,'username' => $username];
return array('username' => $username);
}
and I have to use the relationship among classes
use Doctrine\Common\Collections\ArrayCollection;
class Experience {
/**
* #ORM\OneToMany(targetEntity="FosUser", mappedBy="experience")
*/
protected $fosUsers;
public function __construct()
{
$this->fosUsers = new ArrayCollection();
}
}
and
class FosUser {
/**
* #ORM\ManyToOne(targetEntity="Experience", inversedBy="fosUsers")
* #ORM\JoinColumn(name="experience_id", referencedColumnName="id")
*/
protected $fosUsers;
}
and i have always an error
In Symfony you cannot return an array in Action function!, Action function must always return a Response object...So if you want to return data to browser in Symfony, Action function have to return a string wrapped up in Response object.
In your controller code, to return the array to browser, You can serialize an array to JSON and send it back to browser:
public function getUserAction($id)
{
$username = $this->getDoctrine()
->getRepository('voltaireGeneralBundle:FosUser')
->find($id);
if (!$username) {
throw $this->createNotFoundException('No user found for id '.$id);
}
return new Response(json_encode(array('username' => $username)));
}
I suggest you to read more about HTTP protocol , PHP, and Symfony.
I'm using HWIOAuthBundle to let an user login with Oauth, I've created a custom user provider which creates an user in case it doesn't exist:
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$attr = $response->getResponse();
switch($response->getResourceOwner()->getName()) {
case 'google':
if(!$user = $this->userRepository->findOneByGoogleId($attr['id'])) {
if(($user = $this->userRepository->findOneByEmail($attr['email'])) && $attr['verified_email']) {
$user->setGoogleId($attr['id']);
if(!$user->getFirstname()) {
$user->setFirstname($attr['given_name']);
}
if(!$user->getLastname()) {
$user->setLastname($attr['family_name']);
}
$user->setGoogleName($attr['name']);
}else{
$user = new User();
$user->setUsername($this->userRepository->createUsernameByEmail($attr['email']));
$user->setEmail($attr['email']);
$user->setFirstname($attr['given_name']);
$user->setLastname($attr['family_name']);
$user->setPassword('');
$user->setIsActive(true);
$user->setGoogleId($attr['id']);
$user->setGoogleName($attr['name']);
$user->addGroup($this->groupRepository->findOneByRole('ROLE_USER'));
$this->entityManager->persist($user);
}
}
break;
case 'facebook':
if(!$user = $this->userRepository->findOneByFacebookId($attr['id'])) {
if(($user = $this->userRepository->findOneByEmail($attr['email'])) && $attr['verified']) {
$user->setFacebookId($attr['id']);
if(!$user->getFirstname()) {
$user->setFirstname($attr['first_name']);
}
if(!$user->getLastname()) {
$user->setLastname($attr['last_name']);
}
$user->setFacebookUsername($attr['username']);
}else{
$user = new User();
$user->setUsername($this->userRepository->createUsernameByEmail($attr['email']));
$user->setEmail($attr['email']);
$user->setFirstname($attr['first_name']);
$user->setLastname($attr['last_name']);
$user->setPassword('');
$user->setIsActive(true);
$user->setFacebookId($attr['id']);
$user->setFacebookUsername($attr['username']);
$user->addGroup($this->groupRepository->findOneByRole('ROLE_USER'));
$this->entityManager->persist($user);
}
}
break;
}
$this->entityManager->flush();
if (null === $user) {
throw new AccountNotLinkedException(sprintf("User '%s' not found.", $attr['email']));
}
return $user;
}
The problem is that twitter for example doesn't give the email or i want some additional fields to be included before a new user is created. Is there a way to redirect an user to a "complete registration" form before creating it?
I've tried to add a request listener, that on each request, if the user is logged, checks if the email is there and if it doesn't it redirects to the complete_registration page, but it will redirect also if the user goes to the homepage, to logout or anything else, I want to redirect him only if he tries to access some user restricted pages.
Or better, don't create it until he gives all the required informations.
I've found the solution by myself, I've manually created a new exception:
<?php
namespace Acme\UserBundle\Exception;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use HWI\Bundle\OAuthBundle\Security\Core\Exception\OAuthAwareExceptionInterface;
/**
* IncompleteUserException is thrown when the user isn't fully registered (e.g.: missing some informations).
*
* #author Alessandro Tagliapietra http://www.alexnetwork.it/
*/
class IncompleteUserException extends AuthenticationException implements OAuthAwareExceptionInterface
{
private $user;
private $accessToken;
private $resourceOwnerName;
/**
* {#inheritdoc}
*/
public function setAccessToken($accessToken)
{
$this->accessToken = $accessToken;
}
/**
* {#inheritdoc}
*/
public function getAccessToken()
{
return $this->accessToken;
}
/**
* {#inheritdoc}
*/
public function getResourceOwnerName()
{
return $this->resourceOwnerName;
}
/**
* {#inheritdoc}
*/
public function setResourceOwnerName($resourceOwnerName)
{
$this->resourceOwnerName = $resourceOwnerName;
}
public function setUser($user)
{
$this->user = $user;
}
public function getUser($user)
{
return $this->user;
}
public function serialize()
{
return serialize(array(
$this->user,
$this->accessToken,
$this->resourceOwnerName,
parent::serialize(),
));
}
public function unserialize($str)
{
list(
$this->user,
$this->accessToken,
$this->resourceOwnerName,
$parentData
) = unserialize($str);
parent::unserialize($parentData);
}
}
In this way, in the custom Oauth user provider when i check if an user exist or I create a new user i check if the required fields are missing:
if (!$user->getEmail()) {
$e = new IncompleteUserException("Your account doesn't has a mail set");
$e->setUser($user);
throw $e;
}
In that case the user will be redirected to the login form, with that exception in session, so in the login page I do:
if($error instanceof IncompleteUserException) {
$session->set(SecurityContext::AUTHENTICATION_ERROR, $error);
return $this->redirect($this->generateUrl('register_complete'));
}
And it will be redirected to a form with the $user in the exception so it can ask only for the missing information and then login the user.
I ran into a similar issue while migrating to a sf site. After the migration I wanted the migrated users to complete their profile whereas the newly registered users could get started immediately.
I solved this similar to your idea using a RequestListener but added a whitelist of pages that the user is allowed without completion of his profile. Depending on the number of pages you want the user to have access to without completion of his profile you might also consider using a blacklist.
I did not check for th existance of a particular field uppon redirecting to the profile completion page but added a role "ROLE_MIGRATION" when migrating the usersdatabase. In your case you can add the role when creating the user through oauth.
Here the code of my request listener:
public function onRequest(GetResponseEvent $evt)
{
if (HttpKernelInterface::MASTER_REQUEST !== $evt->getRequestType())
{
return;
}
$token = $this->securityContext->getToken();
if(!is_object($token)) or not migrating
{
// user is not logged in
return;
}
$user = $token->getUser();
if(!$user instanceof User || !$user->hasRole('ROLE_MIGRATING'))
{
// different user class or already migrated
return;
}
$openPaths = array(
'/start-2.0',
'/css',
'/js',
'/images',
'/media',
'/geo',
'/_wdt',
'/logout', or not migrating
'/terms',
'/contact',
'/about',
'/locale/set'
);
foreach($openPaths as $p)
{
if(strpos($evt->getRequest()->getPathInfo(),$p)===0)
{
// path is open for migrating users
return;
}
}
header('Location: /start-2.0');
exit;
}