FOSUserBundle : one of two fields mandatory - symfony

I'm using FOSUserBundle in Symfony2, and I would like the user to enter at least one phone number between the home phone number and the mobile phone number, but I can't find where to add a check for at least one field filled.
Any clues ?
Thanks

You could make yourself a class constraint. Look at Symfony's cookbook section on custom constraints. At the bottom there is a small section on class constraint validators.
You would have a constraint like this:
class RegistrationPhones extends Constraint
{
public $message = '';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return 'user_phones_validator';
}
}
Then your validator would look like this:
class RegistrationPhonesValidator extends ConstraintValidator
{
public function validate($registration, Constraint $constraint)
{
$homePhone = $registration->getHomePhone();
$mobilePhone = $registration->getMobilePhone();
if (empty($homePhone) && empty($mobilePhone)) {
$this->context->addViolationAt('homePhone', $constraint->message, array(), null);
}
}
}
Finally, your validation.yml would have an entry like so:
User\MyBundle\Form\Model\Registration:
constraints:
- User\MyBundle\Validator\Constraints\RegistrationPhonesValidator:
message: phones.at_least_one_must_be_set
This should do the trick.

Related

Unit testing custom validator

My validator looks like:
<?php
declare(strict_types=1);
namespace App\Infrastructure\Domain\Model\Company\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Contracts\Translation\TranslatorInterface;
final class ContainsUnsupportedSymbolsValidator extends ConstraintValidator
{
public function __construct(
private TranslatorInterface $translator
)
{
}
public function validate($value, Constraint $constraint): void
{
if (!$constraint instanceof ContainsUnsupportedSymbols) {
throw new UnexpectedTypeException($constraint, ContainsUnsupportedSymbols::class);
}
// custom constraints should ignore null and empty values to allow
// other constraints (NotBlank, NotNull, etc.) to take care of that
if (null === $value || '' === $value) {
return;
}
if (!is_string($value)) {
// throw this exception if your validator cannot handle the passed type so that it can be marked as invalid
throw new UnexpectedValueException($value, 'string');
}
if (!preg_match('/^\+?[0-9]{3}-?[0-9]{6,12}$/', $value, $matches)) {
// the argument must be a string or an object implementing __toString()
$this->context->buildViolation(
$this->translator->trans($constraint->message, ['{phone}' => $value])
)->addViolation();
}
}
}
The test I wrote for valid case is:
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Infrastructure\Domain\Model\Company\Validator;
use App\Infrastructure\Domain\Model\Company\Validator\ContainsUnsupportedSymbols;
use App\Infrastructure\Domain\Model\Company\Validator\ContainsUnsupportedSymbolsValidator;
use App\Tests\Unit\UnitTestCase;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class ContainsUnsupportedSymbolsValidatorTest extends UnitTestCase
{
public function testValidatorWithValidPhone():void
{
$phone = "+359883202020";
$translationMock = $this->createMock(TranslatorInterface::class);
$constraint = new ContainsUnsupportedSymbols();
$constraintValidator = new ContainsUnsupportedSymbolsValidator($translationMock);
$this->assertEmpty($constraintValidator->validate($phone, $constraint));
}
}
Is it correct to test with assertEmpty when the validate method return type is void?
And how to test with incorrect data? Tried to mock the context and both buildViolation and addViolation methods but got error:
Error: Call to a member function buildViolation() on null
This is what I tried with incorrect phone
public function testValidatorWithInvalidPhone():void
{
$phone = "+359()883202020";
$translationMock = $this->createMock(TranslatorInterface::class);
$contextMock = $this->createMock(ExecutionContextInterface::class);
$contextMock->expects($this->once())
->method('buildViolation')
->with('message');
$contextMock->expects($this->once())
->method('addViolation');
$constraint = new ContainsUnsupportedSymbols();
$constraintValidator = new ContainsUnsupportedSymbolsValidator($translationMock);
$this->assertEmpty($constraintValidator->validate($phone, $constraint));
}
Your context is null. You have to initialize the ConstraintValidator extended by your custom validator.
The question has found an answer here: Unit testing a custom symfony constraint
In short here is an example https://github.com/symfony/symfony/blob/6.2/src/Symfony/Component/Validator/Tests/Constraints/BlankValidatorTest.php
In the example the test class extends ConstraintValidatorTestCase that defines a context (look here: https://github.com/symfony/validator/blob/6.1/Test/ConstraintValidatorTestCase.php)

Use several times global parameter in controller symfony

I need your help in Symfony controller, there is a way to use a global parameter and get the different value in different method.
Actually I have this.
class ArticleController extends Controller
{
//Injection of white october bundle
/** #DI\Inject("white_october_breadcrumbs") */
private $wob;
public function indexAction(Request $request)
{
$this->wob->addRouteItem("Article", "article_index");
//Some stuff
}
public function addAction(Request $request, $id=0)
{
if($request->get('_route') === "article_add"){
$this->wob->addRouteItem("Add article", "article_add");
} else {
$this->wob->addRouteItem("Edit article", "article_edit");
}
//Some stuff
}
//Other functions..
}
As you can see, actually my breadcrumb only keep the last value of the global parameter $wob
Example :
Home > Article
Home > Edit article
But i want :
Home > Article > Edit article
Don't know if it's possible
Thank you in advance !
Your addAction is completely separated from indexAction and these are different requests, so you cannot expect to keep value of $wob property between requests.
In yours example you can make parent only inside the same action:
public function addAction(Request $request, $id=0)
{
$this->wob->addRouteItem("Article", "article_index");
if($request->get('_route') === "article_add"){
$this->wob->addRouteItem("Add article", "article_add");
} else {
$this->wob->addRouteItem("Edit article", "article_edit");
}
//Some stuff
}

How to migrate users passwords to Symfony FOSUserBundle table

I have an old website which I want to migrate to Symfony2 and use the FOSUserBundle.
My 'old' website's database stores encrypted passwords as follows:
sha1(\"$salt1$plain_text_password$salt2\")
However, I've not done this before and am not sure on how to go about doing it. Is my only option to somehow configure FOSUserBundle to use the same encryption as the old website? If so, where would I do this?
You can create a custom password encoder and override BasePasswordEncoder ::isPasswordValid() add your logic in it
example
class CustomPasswordEncoder extends BasePasswordEncoder
{
public function encodePassword($raw,$salt){
list($salt1,$salt2) = explode(",",$salt);
return sha1($salt1.$raw.$salt2); // your logic here
}
public function isPasswordValid($encoded,$raw,$salt)
{
return $this->comparePasswords(
$encoded,$this>encodePassword($raw,$salt));
}
}
make this class a service
service.yml
services:
custom-password-encoder:
class: path\to\CustomPasswordEncoder
and add this on your security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: {id: custom-password-encoder}
you also need to change User::getSalt() to return the two salts separated by comma
example
Class User extends BaseUser
{
public function getSalt()
{
return "salt1,salt2";
}
}
Snippet for Magento migration password logic.
<?php
namespace AppBundle\Utils;
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
class CustomPasswordEncoder extends BasePasswordEncoder
{
public function encodePassword($raw, $salt)
{
$salt2 = base64_encode($salt.uniqid());
// logic from magento
return md5($salt2.$raw).":".$salt2;
}
public function isPasswordValid($encoded, $raw, $salt)
{
// magento logic
$hashArr = explode(':', $encoded);
$hashToValidate = md5($hashArr[1] . $raw);
return $this->comparePasswords(
$hashArr[0], // first piece of password
$hashToValidate // $salt.$password md5 hash
);
}
}

filter boolean variable in a twig template

I have a boolean variable(0, 1) in my database and I want to filter it to a word 0 for 'NO', and 1 for 'Yes'. how can I do that in a twig template
I want something like {{ bool_var | '??' }} where the '??' is the filter
Quick way to achieve that is to use the ternary operator:
{{ bool_var ? 'Yes':'No' }}
http://twig.sensiolabs.org/doc/templates.html#other-operators
You could also create a custom filter that would do this. Read about custom TWIG extensions - http://symfony.com/doc/current/cookbook/templating/twig_extension.html
To build on what #dmnptr said in his last paragraph, in your app bundle, create a /Twig folder and create an AppExtension class inside.
class AppExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('boolean', array($this, 'booleanFilter')),
);
}
public function booleanFilter($value)
{
if ($value) {
return "Yes";
} else {
return "No";
}
}
public function getName()
{
return 'app_extension';
}
}
Then, in your bundle's Resources/config/ folder, add the following to your services.yml where class is the class of the new class:
app.twig_extension:
class: [YourAppBundleNamespace]\Twig\AppExtension
public: false
tags:
- { name: twig.extension }
The filter will be available in Twig by simply appending a |boolean to any variable.
Or even better you could make a boolean to string transformer and add it to your form.
It might be 'more' code but the upside is reusability. You wouldn't have to make your templates dirty with logic and you could reuse it to all the forms you want :)
Pros:
Not tied to the form component so you can still use it.
Use it anywhere, more functionality than a twig extension.
No need to mess with twig or symfony configuration.
Can use it in forms themselves.
Documentation:
http://symfony.com/doc/current/cookbook/form/data_transformers.html
Example from:
Symfony2 Forms BooleanToStringTransformer Issue
<?php
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class BooleanToStringTransformer implements DataTransformerInterface
{
private $trueValue;
private $falseValue;
public function __construct($trueValue, $falseValue)
{
$this->trueValue = $trueValue;
$this->falseValue = $falseValue;
}
public function transform($value)
{
if (null === $value) {
return null;
}
if (!is_bool($value)) {
throw new TransformationFailedException('Expected a Boolean.');
}
return true === $value ? $this->trueValue : $this->falseValue;
}
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
if (!is_string($value)) {
throw new TransformationFailedException('Expected a string.');
}
return $this->trueValue === $value;
}
}

Custom Validator doesn't output error message

I have installed a custom validator which checks if the generated slug is unique.
Now I am testing the validator and it seems that the validator works (form doesn't get persisted) but I don't get an error message...
class Unique extends Constraint
{
public $message = 'The value of "%property%" already exists.';
public $property;
public function getDefaultOption()
{
return 'property';
}
public function getRequiredOptions()
{
return array('property');
}
public function validatedBy()
{
return 'loc_article_validator_unique_alias';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
The form errors are rendered through {{ form_rest(form) }} in twig
So I found the issue.
The Issue was that that Custom Constraints errors can't get rendered over foreach. They have to get rendered through
{{ form_errors(form) }}
My remaining questions are now:
1.) How can I render the Custom Constrain Errors like all other errors?
2.) Why does the Custom class extending Constrain requires an alias of the CustomValidator service?
By these lines
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
You make the constraint a class constraint wich means the errors will show up on top of the whole form and not next to the field.
Try defining it as property constraint
public function getTargets()
{
return self::PROPERTY_CONSTRAINT;
}
If this does not help, please post your validation definition and the form builder code that generates the form.

Resources