Symfony 4 change password by username - email can not be null - php

Introduction
I have been trying to figure out how to create a reset password form that's governed by username value.
The Error
Path Message Invalid value Violation
data.email This value should not be blank. null
ConstraintViolation {#945 ▼
-message: "This value should not be blank."
-messageTemplate: "This value should not be blank."
-parameters: [▶]
-plural: null
-root: Form {#620 ▶}
-propertyPath: "data.email"
-invalidValue: null
-constraint: NotBlank {#477 …}
-code: "c1051bb4-d103-4f74-8988-acbcafc7fdc3"
-cause: null
}
What's expected
Update my User Object with the new password.
My Code
ForgotController.php
I know this probably isn't the correct way to get the password, but searching Symfony 4 forgotten password form brings up symfony2.4 posts which aren't relevant to my version
<?php
namespace App\Controller\User;
use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class ForgotController extends Controller
{
public function forgot(Request $request, UserPasswordEncoderInterface $encoder)
{
$entityManager = $this->getDoctrine()->getManager();
$changePassword = $request->request->get('change_password');
$username = $changePassword['username'];
$password = $changePassword['plainPassword']['first'];
$user = $entityManager->getRepository(User::class)->findBy(['username' => $username]);
$userEntity = new User();
if (!$user) {
$this->addFlash('danger', 'User not found for '. $username);
}
$form = $this->createForm(ChangePasswordType::class, $userEntity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$pass = $encoder->encodePassword($userEntity, $password);
$userEntity->setPassword($pass);
$entityManager->flush();
$this->addFlash('success', 'Password Changed!');
} catch (Exception $e) {
$this->addFlash('danger', 'Something went skew-if. Please try again.');
}
return $this->redirectToRoute('login');
}
return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
}
}
ChangePasswordType.php
<?php
namespace App\Form\User;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', TextType::class)
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'New Password'),
'second_options' => array('label' => 'Repeat New Password')
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class
));
}
}
forgot.html.twig
{% include 'builder/header.html.twig' %}
<div class="user-container" id="user-content">
{% block body %}
{% include 'builder/notices.html.twig' %}
<div class="user-container">
<i class="fas fa-user-edit fa-5x"></i>
</div>
<hr />
{{ form_start(form) }}
{{ form_row(form.username, { 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.plainPassword.first, { 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.plainPassword.second, { 'attr': {'class': 'form-control'} }) }}
<div class="register-btn-container">
<button class="btn btn-danger" id="return-to-dash-btn" type="button">Cancel!</button>
<button class="btn btn-primary" type="submit">Update!</button>
</div>
{{ form_end(form) }}
{% endblock %}
</div>
{% include 'builder/footer.html.twig' %}
I'm not sure why email is even being mentioned unless it's trying to insert a new user into a database but it shouldn't be trying to do that based on my controller? How can I go about adding a forgot password form that's identified by username?

Since your change password form only needs two fields we will use an array instead of a user entity. Need a slight tweak to ChangePasswordType:
// ChangePasswordType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
//'data_class' => User::class
));
}
Here is a working forgot action:
public function forgot(
Request $request,
UserPasswordEncoderInterface $encoder,
UserRepository $userRepository)
{
$userInfo = ['username' => null, 'plainPassword' => null];
$form = $this->createForm(ChangePasswordType::class, $userInfo);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$userInfo = $form->getData();
$username = $userInfo['username'];
$plainPassword = $userInfo['plainPassword'];
$user = $userRepository->findOneBy(['username' => $username]);
if ($user === null) {
$this->addFlash('danger', 'Invalid username');
return $this->redirectToRoute('forgot');
}
$password = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($password);
$userRepository->flush();
return $this->redirectToRoute('login');
}
return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
}
UserRepository is injected which get's rid of all the doctrine nonsense. There is one caveat here that I'll get back to.
We build the userInfo array and let the form processing do it's thing. We really don't want to mess with getting attributes directly from the request object if we don't have to.
Then we get our actual user entity to update. Notice the use of findOneBy instead of findBy. We check to make sure username is valid. If you really wanted to get fancy then you could add a validation constraint to the form to do this check automatically.
I got rid of all the try/catch stuff. It just clutters up your code. By this point if an exception is thrown then it is truly exceptional and can be handled by the default exception handlers.
You got the password encoder stuff just right.
And then instead of $entityManager->flush() I used $userRepository->flush(); Out of the box there is no flush method on repositories so you need to add one:
// UserRepository
public function flush()
{
$this->_em->flush();
}
I personally like dealing with just repositories and not the entity manager. But if want, you can just go back and inject the manager instead of the repository. Your call.
And as mentioned in the comments, you do want to add some security to prevent users from changing other users passwords.

Implement something along the lines of below - I have left pieces out like the templates and routing. This is just to help you along.
Form 1: ForgottenUserType - use enters only username/email and submits
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("username", null, array(
"label" => "Email",
"attr" => array(
"class" => "form-control",
"id" => "basic-url",
"placeholder" => "Email address for your account"
),
"constraints" => array(
new Email(array("message" => "Invalid Email"))
)
));
}
Form 2: ChangePasswordFormType - user enters & repeats new password.
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'required' => false,
'first_options' => array('label' => 'New password'),
'second_options' => array('label' => 'Confirm new password'),
'invalid_message' => 'The password fields must match.',
))
;
}
Controller: ResetPasswordController - handles user lookup request of Form 1 and Password reset request for Form 2:
<?php
namespace App\Controller\User;
use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class ResetPasswordController extends Controller
{
/**
* Action for when a user has forgotten their password, to request ForgottenUser form
*
* #param Request $request
*/
public function requestAction(Request $request)
{
$tmpUser = new User();
$entityManager = $this->getDoctrine()->getManager();
$form = $this->createForm(ForgottenUserType::class, $tmpUser);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $entityManager->getRepository(User::class)->findBy(['username' => $tmpUser->getUsername()]);
if ($user) {
//check and set token
if (null === $user->getConfirmationToken()) {
/** #var $tokenGenerator TokenGeneratorInterface */
$token = md5(uniqid($user->getUsername(), true)); //some unique token (you can create a nicer token generator in standalone class with a service)
$user->setConfirmationToken($token);
$user->setPasswordRequestedAt(new \DateTime());
$em->persist($user);
$em->flush();)
$this->addFlash('Info', 'If user is found, you will receive an email with further instructions.');
//send email using swiftmailer & include url with token
}
} else {
//return to requestAction.
}
}
//request template contains the ForgottenUserType form
return $this->render(":path/to/template:request.html.twig", array(
"forgotten_form" => $form->createView()
));
}
/**
* Reset user password.
*
* #param Request $request
* #param $token
*/
public function resetAction(Request $request, $token)
{
$entityManager = $this->getDoctrine()->getManager();
$user = $entityManager->getRepository(User::class)->findBy(['confirmationToken' => $token]);
if (null === $user) {
return new RedirectResponse($this->generateUrl('resetting_request')); //to requestAction above. / create route
}
$form = $this->createForm(ChangePasswordFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->SetConfirmationToken(null);
$user->setPasswordRequestedAt(null);
$entityManager->persist($user);
$entityManager->flush()
$this->addFlash("success", "Your password has been reset, log in now.");
$url = $this->generateUrl('app.login'); //route to login page
$response = new RedirectResponse($url);
return $response;
}
//reset template contains the ChangePasswordFormType form
return $this->render(':path/to/forgottenpasswordtemplate:reset.html.twig', array(
'token' => $token,
'form' => $form->createView(),
));
}
}

Related

Symfony - controller edit action

I defined the form in my controller and set function to edit/update specific fields that I can find by ID. I can't find what am I doing wrong..
public function getUserEdit(Request $request)
{
$form = $this->createFormBuilder()
->add('firstName', TextType::class, array('label' => 'First Name*', 'attr' => ['class'=>'form-control']))
->add('save', SubmitType::class, array('label' => 'Send', 'attr' => [
'class' => 'btn btn-primary action-save'
]))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$firstName = $data['firstName'];
$this->container->get('user')->editUser($firstName);
}
return $this->success();
}
Service
public function editUser($id)
{
$editUsers = $this->getUserRepository()->find($id);
if(empty($editUsers)) {
$editUsers = new User();
$editUsers->setId($id);
$this->em->persist($editUsers);
$this->em->flush();
}
}
Symfony returns a single object when searching by find primary key (usually id), then you can check if exist returns a single Entity (in this case User) and so that checking by using insteance.
public function editUser($id)
{
$editUsers = $this->getUserRepository()->find($id);
if($editUsers insteance User) { // here just check
//$editUsers = new User(); // here You don't need create new object of class User, just remove
$editUsers->setId($id);
$this->em->persist($editUsers);
$this->em->flush();
}
else {
// do something if you need
}
}

Search filter with method get doesn't found results [symfony2]

I have a search form that works with the method POST, but the method POST doesn't display the requested data in the url.
With method POST the url look like this:
/search_flight
with the method GET no results found, the url look like this:
/search_flight?from=Cape+Town%2C+International+CPT&to=Johannesburg%2C+O.R.+Tambo+International+JNB&departuredate=2016%2F01%2F08&arrivaldate=2016%2F10%2F04&price=57.5%2C1000
I also noticed that with the method GET the data is reset in each input of the form.
routing.yml
searchFlight:
path: /search_flight
defaults: { _controller: FLYBookingsBundle:Post:searchtabflightResult }
requirements:
_method: GET|POST
controller
This method send the requested data to the method searchtabflightResultAction that will handle the query.
public function searchtabflightAction()
{
//$form = $this->createForm(new SearchflightType(),null, array('action' => $this->generateUrl('searchFlight'),'method' => 'GET',));
$form = $this->get('form.factory')->createNamed(null, new SearchflightType());
return $this->render('FLYBookingsBundle:Post:searchtabflight.html.twig', array(
'form' => $form->createView(),
));
}
.
<form action="{{ path ('searchFlight') }}" method="GET">
{# here I have my forms #}
</form>
.
public function searchtabflightResultAction(Request $request)
{
//$form = $this->createForm(new SearchflightType());
$form = $this->get('form.factory')->createNamed(null, new SearchflightType());
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
$airport1 = $form["to"]->getData();
$airport = $form["from"]->getData();
$departureDateObj = $form["departuredate"]->getData();
$arrivalDateObj = $form["arrivaldate"]->getData();
$price = $form["price"]->getData();
$entities = $em->getRepository('FLYBookingsBundle:Post')->searchflight($airport1,$airport,$departureDateObj,$arrivalDateObj,$price);
return $this->render('FLYBookingsBundle:Post:searchtabflightResult.html.twig', array(
'entities' => $entities,
'form' => $form->createView(),
));
}
How can I make my search filter works with method get ?
Everything should be done within two actions, the basic concept is:
SearchFlightType has with/wo price option:
class SearchFlightType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('from', FormType\TextType::class)
->add('to', FormType\TextType::class)
->add('departuredate', FormType\TextType::class)
->add('arrivaldate', FormType\TextType::class);
if ($options['price']) {
$builder->add( 'price', FormType\TextType::class );
}
$builder
->add('submit', FormType\SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'price' => false,
));
}
}
Controller.php
class PostController extends Controller
{
/**
* #Route("/index", name="index")
*/
public function indexAction(Request $request)
{
$defaultData = array();
$form = $this->createForm(SearchFlightType::class, $defaultData, array(
// action is set to the specific route, so the form will
// redirect it's submission there
'action' => $this->generateUrl('search_flight_result'),
// method is set to desired GET, so the data will be send
//via URL params
'method' => 'GET',
));
return $this->render('Post/searchtabflight.html.twig', array(
'form' => $form->createView(),
));
}
/**
* #Route("/search_flight_result", name="search_flight_result")
*/
public function searchTabFlightResultAction(Request $request)
{
$defaultData = array();
$entities = null;
$form = $this->createForm(SearchFlightType::class, $defaultData, array(
// again GET method for data via URL params
'method' => 'GET',
// option for price form field present
'price' => true,
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// get data from form
$data = $form->getData();
// process the data and get result
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('FLYBookingsBundle:Post')->searchflight($data['from'], $data['to'], ...);
}
return $this->render('Post/searchtabflight.html.twig', array(
'form' => $form->createView(),
// present the result
'entities' => $entites,
));
}
}

Form Dynamic Symfony2 ManyToMany

I have 2 Entities: Company and User that have a link company_user ManyToMany
I would like to be able to display the list of companies linked to the connected user.
So I followed this: http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-user-data
However, in my request, I can not display the company related to the user.
Here is my CompanyRepo
<?php
namespace BudgetBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* CompanyRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CompanyRepository extends EntityRepository
{
public function companyByUser($user){
return $qb = $this->createQueryBuilder('cr')
->where('cr.id = :user')
->setParameter('user', $user);
}
}
My FOrmType
public function buildForm(FormBuilderInterface $builder, array $options)
{
// grab the user, do a quick sanity check that one exists
$user = $this->tokenStorage->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'The FriendMessageFormType cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'class' => Company::class,
'property' => 'name',
'query_builder' => function (CompanyRepository $er) use ($user) {
// build a custom query
// return $er->createQueryBuilder('u')->addOrderBy('fullName', 'DESC');
return $er->companyByUser($user);
// or call a method on your repository that returns the query builder
// the $er is an instance of your UserRepository
// return $er->createOrderByFullNameQueryBuilder();
},
);
// create the field, this is similar the $builder->add()
// field name, field type, data, options
$form->add('company', EntityType::class, $formOptions);
}
);
}
And my controller
public function addDebitAction( Request $request ) {
$em = $this->getDoctrine()->getManager();
$debit = new Debit();
$form = $this->createForm( DebitType::class, $debit );
$amountCapital = $em->getRepository('BudgetBundle:Capital')->find($this->getUser());
if ( $request->isMethod( 'POST' ) && $form->handleRequest( $request )->isValid() ) {
$em = $this->getDoctrine()->getManager();
$capital = $em->getRepository( 'BudgetBundle:Capital' )->findOneBy( [ 'user' => $this->getUser()->getId() ] );
if ($capital == false){
$this->get('session')->getFlashBag()->set('error', 'Vous devez avoir un capital de départ, merci d\'en ajouter un !');
return $this->redirectToRoute('budget_add_gain');
}
else{
$capital->setTotalDebits($capital->getTotalDebits() + $debit->getAmount());
$amountCapital->setAmount($amountCapital->getAmount() - $debit->getAmount()); // MEt a jout le capital
}
$em = $this->getDoctrine()->getManager();
$debit->setUser( $this->getUser() );
$em->persist( $debit );
$em->flush();
$request->getSession()->getFlashBag()->add( 'success', 'Debit Added !' );
return $this->redirectToRoute( 'budget_homepage' );
}
return $this->render( '#Budget/Views/company/debit.html.twig', [ 'form' => $form->createView() ] );
}
Try this:
public function companyByUser($user){
return $this->createQueryBuilder('cr')
->innerJoin('cr.users', 'u', 'WITH', 'u = :user')
->setParameter('user', $user);
}
You also need to set a choice_label on your query builder in the form type. Something like :
choice_label => 'company_name'
I don't know what your company and user entities fields are, so make sure you replace users and company_name with the correct fields names.

Rendering multiple forms on same page with Symfony 3

I'd like to show a login and a registration form on the same page. However it seems that we cannot render the same template with different form variables in different two actions in a controller.
Here is what I mean;
class SecurityController extends Controller {
/**
* #Route("/", name="login")
*/
public function loginAction(Request $request)
{
//Check if the user is already authenticated
if($this->container->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
return $this->redirect($this->generateUrl('dashboard'));
}
$authenticationUtils = $this->get('security.authentication_utils');
// get the error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
$this->addFlash(
'welcome',
'Welcome back!'
);
return $this->render(
'::landing.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
$registrationform = $this->get('form.factory')->createNamedBuilder('registrationform', UserType::class);
}
/**
* #Route("/register", name="register")
*/
public function registerAction(Request $request)
{
//Check authentication
if($this->container->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
return $this->redirect($this->generateUrl('dashboard'));
}
// get the authentication utils
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
// build the form
$user = new User();
$form = $this->createForm(UserType::class, $user);
// handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setRoles(array('ROLE_USER'));
// Encode the password (you could also do this via Doctrine listener)
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
// save the User!
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->get('security.token_storage')->setToken($token);
$this->get('session')->set('_security_main', serialize($token));
// ... do any other work - like sending them an email, etc
// maybe set a "flash" success message for the user
$this->addFlash(
'success',
'Please complete your profile now.'
);
$message = \Swift_Message::newInstance()
->setSubject('You have successfully signed up!')
->setFrom('no-reply#kampweb.com')
->setTo($user->getUsername())
->setBody($this->renderView(
':emails:registration.html.twig'),'text/html');
$this->get('mailer')->send($message);
return $this->redirectToRoute('update');
} else{
$errors = $this->get('validator')->validate( $user );
}
return $this->render(
'::landing.html.twig',
array( 'form' => $form->createView(),
'last_username' => $lastUsername,
'error' => $error,
)
);
}
}
Above I have my login and registration action. I'd like to pass the 'form' variable to the same twig template that I render with login action. When I try to do that I'm getting the following exception.
Variable "form" does not exist in ::landing.html.twig at line 50
500 Internal Server Error - Twig_Error_Runtime
I would do it like this
2 twig files -
login.html.twig and register.html.twig - Every file render by himself the action
now the third twig file called baseLogin.html.twig
in this file, I render the two files (login and register) by calling controller
For example
baseLogin.html.twig
<div>
{{ render(controller('UserBundle:Security:login')) }}
</div>
<div>
{{ render(controller('UserBundle:Registration:register')) }}
</div>

silex passing form data to another page?

I'm really new to silex and symfony. This is my first foray into silex. I've got code that created my form in my app.php file from a little hacking and copying and pasting from documentation.
Now how do i pass this data to another page?
I would like to create a page that just dumps the post/get array to give me an idea how to pass around get/post variables.
Here's part of my app file:
<?php
/** /src/app.php */
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Register new application
*/
$app = new Application();
// skip to the form part ...
$app->match('/', function (Request $request) use ($app) {
// some default data for when the form is displayed the first time
$data = array(
'name' => 'Your name',
'email' => 'Your email',
);
$form = $app['form.factory']->createBuilder('form', $data)
->add('name')
->add('email')
->add('gender', 'choice', array(
'choices' => array(1 => 'male', 2 => 'female'),
'expanded' => true,
))
->getForm();
if ('POST' == $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// do something with the data
// redirect somewhere
return $app->redirect('completed');
}
}
// display the form
return $app['twig']->render('index.html', array('form' => $form->createView()));
});
Would i then create a page like so?
<?php // app.php
$app->match('complete') use function ($app) {
// sorry psuedocode
foreach ($REQUEST as $key=> $var) {
echo "$key: $var";
}
}
You could try using a forward. http://silex.sensiolabs.org/doc/usage.html#fowards
// where params is the values from your POST
$subRequest = Request::create('/otherpage', 'GET', $params);
return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST);

Categories