Simple recovery password form - forms

again, im trying a simple form for recovery forgot user password.
The form only contains a email input. The form handler, receive the email, search the user, restore your password and send a email with the new password. Is important says that user not is authenticated.
So, i need create a custom constraint that check if the email exists in the db. I have an method of a own service that do it. So, i need bind the method with the constraint.
The custom validation class is:
class ExistEmailValidator extends ConstraintValidator
{
protected $userService;
public function setUserService($userService)
{
echo "Setter...";
var_dump($userService);
die();
$this->userService = $userService;
}
public function validate($value, Constraint $constraint)
{
if($this->userService->existUserEmail($value) == false){
$this->context->addViolation($constraint->message, array('%string%' => $value));
}
}
}
I inject the service from services.yml:
validator.unique.ExistEmailValidator:
class: AppsManantiales\CommonBundle\Validator\Constraints\ExistEmailValidator
tags:
- {name: validator.constraint_validator, alias: user_email_validator}
calls:
- [setUserService, ["userservice"]]
The problem is taht, never inject the service, because Symfony says:
Error: Call to a member function existUserEmail() on a non-object
Note: the user service id is: "userservice".
php app/console container:debug | grep UserService
userservice container AppsManantiales\CommonBundle\Services\UserService
Any ideas ?
UPDATE 1
My constraint class is:
class ExistEmail extends Constraint{
public $message = 'El e-mail "%string%" no pertenece a ningun usuario del sistema.';
public function validatedBy()
{
return get_class($this).'Validator';
}
}
And the form definition is:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('anUserEmail', 'email',
array(
'constraints' => array(
new ExistEmail()
)
));
}

Replace,
public function validatedBy()
{
return get_class($this).'Validator';
}
in your ExistEmail constraint class by,
public function validatedBy()
{
return 'user_email_validator';
}
in order to map your constraint with the right instance of ExistEmailValidator (The one instantiated in the service container).

Related

Get distinct emails of users who commented on a ticket in Laravel

I have the following models.
Ticket
class Ticket extends Model
{
protected $fillable = ['title', 'content', 'slug', 'status', 'user_id'];
protected $guarded = ['id'];
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
public function comments()
{
return $this->hasMany('App\Comment', 'post_id', 'id');
}
public function commenters()
{
return $this->hasManyThrough('App\User', 'App\Comment');
}
}
Comment
class Comment extends Model
{
protected $guarded = ['id'];
public function user()
{
return $this->belongsTo('App\User', 'user_id', 'id');
}
public function ticket()
{
return $this->belongsTo('App\Ticket', 'post_id', 'id');
}
public function post()
{
return $this->morphTo();
}
}
User
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
protected $guarded = ['id'];
protected $hidden = [
'password', 'remember_token',
];
}
A ticket has many comments
A comment has one user
I'm trying to extract a name-string list of users who commented on a ticket, but with no success.
In my controller, I'm using the following code to extract the list of commenters.
Ticket::where('id', $comment->post_id)->commenters
However, I'm getting the error:
Property [commenters] does not exist on the Eloquent builder instance.
You're missing a closure. This query is unfinished:
Ticket::where('id', $comment->post_id)
And an unfinished query is an instance of the Builder class, not a Ticket instance, or Collection of Ticket instances as you're expecting.
If you're expecting a single Ticket instance, then you'd use ->first():
$ticket = Ticket::where('id', $comment->post_id)->with(['commenters'])->first();
$commenters = $ticket->commenters;
If you're expecting multiple Ticket instances, then you'd use ->get():
$tickets = Ticket::where('id', $comment->post_id)->with(['commenters'])->get();
foreach($tickets AS $ticket){
$commenters = $ticket->commenters;
}
Note: ->with(['commenters']) is used to speed up the loading of ->commenters; if you omit it, then a new query is run when you try to access $ticket->commenters.

SYMFONY custom CONSTRAINT -> Pass variable to a custom CONSTRAINT / How CONSTRAINT binded to a form field can OVERRIDE CONSTRAINT in ANNOTATION

My goal: I built a custom constraint in SYMFONY, I needed to pass a variable to that constraint.
The context: The constraint do a check if a value is unique in the DB, if it is not, it raises a CONSTRAINT alert. That works alright when the FORM is used to create a new tuple in the DB but if it is an edit it raises an exception which should be bypass by checking that the value already existing, exists for the tuple Id being edited.
Hence I needed to pass the Id of the tuple being edited to my constraint check.
At first I implemented my custom constraint in my entity:
class MyEntity{
/**
* #MyBundleAssert\CheckValueAlreadyInDB(
* message = "already_exists_in_db",
* fieldToSearch = "my_value",
* tableToSearch = "my_table"
*)
*/
private myValue;
}
As one can see, I did not find a way to implement a way to pass a VARIABLE using the constraint with ANNOTATION. By searching, I understood I could do that by using the __construct() of my custom constraint class:
/**
* #Annotation
*/
class CheckValueAlreadyInDB extends Constraint{
public $message;
public $fieldToSearch;
public $tableToSearch;
public $idToCheck;
public $idToCheckFieldName;
public function __construct($options){
if(count($options)>0){
$this->idToCheck = $options['idToCheck'];
$this->idToCheckFieldName = $options['idToCheckFieldName'];
$this->fieldToSearch = $options['fieldToSearch'];
$this->tableToSearch = $options['tableToSearch'];
$this->message = $options['message'];
}
}
public function validatedBy()
{
return 'validator_check_value_already_in_db';
}
}
And, the ConstraintValidator extended class linked to it:
class CheckValueAlreadyInDBValidator extends ConstraintValidator
{
private $con;
public function __construct($con){
$this->con = $con;
}
public function validate($value, Constraint $constraint)
{
////My stuff to get a record from the DB////
$sel = new PdoSelect($this->con);
$search = $sel->returnRecordsInTableForSpecificKey([$constraint->fieldToSearch],[$value], $constraint->tableToSearch,false);
//////////////////////////////////////////////
$sameId = false;
if($constraint->idToCheck!==null){
$idToCheckInRetrieveRecord = $search->{$constraint->idToCheckFieldName};
$sameId = ($idToCheckInRetrieveRecord==$constraint->idToCheck)?true:false;
}
if($search!=null&&!$sameId){
$this->context->buildViolation($constraint->message)
->setParameter('%string%', $value)
->addViolation();
}
}
}
With service:
validator.unique.check_value_already_in_db:
class: MyBundle\Form\CustomConstraints\CheckValueAlreadyInDBValidator
arguments: ['#doctrine.dbal.default_connection']
tags:
- { name: validator.constraint_validator, alias: validator_check_value_already_in_db }
I my FORM (AbstractType extended class) for the field regarding myValue, I did edit the constraints attribute.
class MyEntityType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
....
$builder->add('myValue',****Type::class,array(
'constraints' => array(
new CheckValueAlreadyInDB(array(
'idToCheck'=>$options['data']->getId(),
'idToCheckFieldName'=>'id',
'fieldToSearch'=>'my_value',
'tableToSearch'=>'my_table',
'message' => "value_already_exists_in_db"))
)
));
...
}
}
I thought that the CONSTRAINT defined in the buildForm() would override the one defined in the * #MyBundleAssert\CheckValueAlreadyInDB(..) of MyEntity class (which should be the default behaviour). But It did not! I had to delete the ANNOTATION above MyEntity to make the constraint work as defined in the buildForm().
Does anyone know if there is a setting that could permit to have a constraint in a buildForm() overriding one existing as an ANNOTATION in MyEntity, but still let the ANNOTATION above a field in MyEntity be the default behavior? Or is there is a way to pass VARIABLE to ANNOTATIONS?
I found the solution.
My mistake was to try to use constraints in class MyEntityType extends AbstractType:
$builder->add('myValue',****Type::class,array(
'constraints' => array(
new CheckValueAlreadyInDB(array(
'idToCheck'=>$options['data']->getId(),
'idToCheckFieldName'=>'id',
'fieldToSearch'=>'my_value',
'tableToSearch'=>'my_table',
'message' => "value_already_exists_in_db"))
)
));
Update:
DON'T USE IT HERE
Have a look at class-constraint-validator section in the doc.
Implement the ConstraintValidator extended class above the class of the Entity where the validator has to execute its check and not above one attribute of the Entity class. That way one can have access to other attributes of the entity and use it as conditionals in the ConstraintValidator extended class.

symfony validator as service not working

I need a validator that needs to query database. This means I have to create a Constraint a ConstraintValidator and setup a service to inject EntityManager.
I did it, and researched official documentation and lots of posts and couldn't make it work. It seems that validatedBy() is not able to start the service (injecting then the EntityManager).
Here is the code I'm struggling with:
services.yml
validator.frontend.class:
class: Project\UsersBundle\Validation\Constraints\ConstrainsActiveValidator
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: validator.constrain_validator, alias: the_alias }
ConstrainsActive.php
namespace Project\UsersBundle\Validation\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
* */
class ConstrainsActive extends Constraint
{
public function validatedBy()
{
return 'the_alias'; // get_class($this).'Validator';
}
}
ConstrainsActiveValidator.php
namespace Project\UsersBundle\Validation\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;
class ConstrainsActiveValidator extends ConstraintValidator
{
protected $em;
public function __construct(EntityManager $v)
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
$this->context->buildViolation('This name sounds totally fake2!')
->atPath('useEmail')
->addViolation();
}
}
Update:
Thanks for the typo issue. It was a mistake when adapting code to posting here. I fixed! :)
Here you are the error I'm always getting:
Attempted to load class "the_alias" from the global namespace in /var/www/Project/current/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Validator/ConstraintValidatorFactory.php line 71. Did you forget a use statement for this class?
I temporally edited the file ConstraintValidatorFactory.php, and did a var_dump for the var $this->validators and get the response:
array (size=4)
'validator.expression' => string
'validator.expression' (length=20)
'Symfony\Component\Validator\Constraints\EmailValidator' => string
'validator.email' (length=15) 'security.validator.user_password' =>
string 'security.validator.user_password' (length=32)
'doctrine.orm.validator.unique' => string
'doctrine.orm.validator.unique' (length=29)
It seems like symfony framework didn't consider the alias "the_alias" set up in my custom service validator.frontend.class, because it didn't come in the var_dump.
I hope this can give you some clue. Thanks!
You code is ok, check the typo in the construct method where you name the EntityManager as $v and ferer it as $m.
Change the construct of ConstrainsActiveValidatorclass as:
public function __construct(EntityManager $v)
{
$this->em = $v;
}
What error do you have?
Just for people that has the same issue. I finally decided to set checking form parameters inside a type, I mean:
Bundle/Form/addType.php
adding an event listener to the $builder object:
public function buildForm(FormBuilderInterface $builder, array $options) {
...
$builder->addEventListener(FormEvents::POST_SUBMIT, function ($event) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) return;
// Checking comes here!!!
// ......
}
}
In my opinion, validators are great but when a project gets bigger, checking information inside the event is the best way. You can also use some constraints that provide symfony as EmailConstraints:
use Symfony\Component\Validator\Constraints\Email as EmailConstraint;
Answering myself. Let's validate that an entered slug for a post in a blog is unique:
Let's assume we have a bundle called AppBundle
1) Create the subfolder structure "Validator/Contraints" inside src/AppBundle.
2) Inside .../Validator/Constraints, create a file called SlugUnique.php (The constraint):
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class SlugUnique extends Constraint
{
public $message = 'Slug "{{ slug }}" already taken';
public function getTargets()
{
return array(self::PROPERTY_CONSTRAINT);
}
public function validatedBy()
{
return 'app.validator.blog.slug_unique';
}
}
3) Create a file called SlugUniqueValidator.php at the same folder level (The constraint validator):
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;
use AppBundle\Entity\Blog as BlogEntity;
/**
* #Annotation
*/
class SlugUniqueValidator extends ConstraintValidator
{
/** #var EntityManager */
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
/** #var BlogEntity $blogEntity */
$blogEntity = $this->context->getRoot()->getViewData();
if (null !== $this->em->getRepository('AppBundle:Blog')->findBy(['slug' => $blogEntity->slug])) {
$this->context->buildViolation($blogEntity->slug)
->atPath('slug')
->addViolation();
}
}
}
4) Create an entry into services.yml with properly tag items:
app.validator_constraints_blog.slug_unique_validator:
class:AppBundle\Validator\Constraints\SlugUniqueValidator
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: 'validator.constraint_validator', alias: 'app.validator.blog.slug_unique' }
Note that the value for the alias parameter must match the value returned by validatedBy in the SlugUnique class constraint.
5) Finally, assign such validation to the corresponding entity field:
namespace AppBundle\Entity;
use AppBundle\Validator\Constraints as ValidatorConstraints;
class Blog
{
...
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255, nullable=false)
* #ValidatorConstraints\SlugUnique
*/
private $slug;
public getSlug()
...
Notice that depending on the Entity fieldName and the form fieldName, that you may have to set the parameter 'property_path' when building the form type. Example:
$builder->add(
'slug', TextType::class,
array('required' => false, 'property_path' => 'slug')
);
Please note that you can inject a custom service into SlugUniqueValidator instead on the EntityManager for better separation of concerns and bests practices.

Symfony2 validation getters error for a specific field

I'm using Symfony2 validation getters to validate if endDate is later than startDate. It works fine except that I want the error to appear for endDate field not above the whole form. All other validation errors appears above the field it validates. Is there any way to do that?
validation part in my Entity:
/**
* #Assert\True(message = "Invalid date")
*/
public function isDatesValid()
{
return ($this->startDate < $this->endDate);
}
I don't think there is a way to achieve that with a getter validation.
Instead you could use a custom constraint: http://symfony.com/doc/current/cookbook/validation/custom_constraint.html
You'll need to overide the getTargets() method in order to access all properties:
class DateRangeConstraint extends Constraint {
public $message = 'Your message';
public function getTargets() {
return Constraint::CLASS_CONSTRAINT;
}
}
class DateRangeConstraintValidator extends ConstraintValidator {
public function validate($obj, Constraint $constraint) {
if ($obj->startDate() < $obj->endDate()) {
$this->context->addViolationAt(
'startDate',
$constraint->message,
array(),
null
);
}
}
}
Assign this constraint to one or both properties.

How render a form many times

im developing the create student requiriment for my application.
I have my Student entity, the entity contains two properties:
User (instance of User entity)
Course (instance of Course entity)
I build the form, but i wish render the form same times via a click button. On this way the administrator could be add any students without refresh the page.
Its is possible ? How manage the submit on the controller ?
Any ideas ? Thanks
NOTE: Im search a similiar Phpmyadmin behavior when add a new record.
What you should do is create a new object and form (e.g. StudentCollection) that allows for adding the student form using the collection type. This will allow you to manage the dynamically adding/removing student forms a lot better.
More on form collections here http://symfony.com/doc/current/cookbook/form/form_collections.html
e.g. Assuming you have a student form called StudentFormType, something like this should work. There's a good example on the link above that you should use if you want to know how to dynamically add/remove student forms as well as handle the submission.
// src/PathTo/YourBundle/Form/Type/StudentCollectionFormType.php
// Form object
class StudentCollectionFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('students', 'collection', array(
'type' => new StudentFormType(),
'allow_add' => true,
'allow_delete' => true,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PathTo\YourBundle\Model\StudentCollection',
));
}
// ...
}
// src/PathTo/YourBundle/Model/StudentCollection.php
namespace PathTo\YourBundle\Model;
// ...
class StudentCollection
{
// ...
protected $students;
public function __construct()
{
$this->students = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getStudents()
{
return $this->students;
}
public function addStudent(Student $student)
{
$this->students->add($student);
}
public function removeStudent(Student $student)
{
$this->students->removeElement($student);
}
}
Then in your controller
// src/PathTo/YourBundle/Controller/StudentController.php
public function editAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$collection = new StudentCollection();
// Prepopulate the collection with students
// ...
$form = $this->createForm(new StudentCollectionFormType(), $collection);
$form->handleRequest($request);
if ($form->isValid()) {
foreach ($collection->getStudents() as $student) {
$em->persist($student);
}
$em->flush();
// redirect back to some edit page
// ...
}
// render some form template
// ...
}