symfony2 override model validation - symfony

i'm using FOS User Bundle and I want to override the validation file FOS/UserBundle/Resources/config/validaiton.xml:
<constraint name="FOS\UserBundle\Validator\Unique">
<option name="property">usernameCanonical</option>
<option name="message">fos_user.username.already_used</option>
<option name="groups">
<!-- <value>Registration</value> -->
<value>Profile</value>
</option>
</constraint>
username is not in my Registration form (I just set it to hidden), that's the validation should not produce any error...
Maybe there is a better way to remove the username of the form...

To remove the username field of the form in a proper way, you should override the RegistrationFormType, creating your own and extending it from the original FOSUserBundle
<?php
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\FormBuilder;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('email', 'email')
->add('plainPassword', 'repeated', array('type' => 'password'))
// your other custom fields, if any.
}
}
Now, you should declare the overrided form as a service, and then tell to FOSUserBundle configuration file that you are using now an overrided form. Here is the complete documentation.

You can put entity validation info in any validation.yml file. So you can do
#validation.yml
FQCN\Of\User\Entity:
constraints:
- FOS\UserBundle\Validator\Unique:
property: usernameCanonical
groups: [Profile]
message: fos_user.username.already_used
properties:
# property validations here

Related

Symfony - Sonata admin - override validation

i'm using sonata admin, i tried to override max length allowed for name of categorie
I have an entity MyEntity who extend Application\Sonata\ClassificationBundle\Entity\Category
// MyEntity admin class
I put this following function, regarding https://sonata-project.org/bundles/core/master/doc/reference/conditional_validation.html#inline-validation
public function validate(\Sonata\Form\Validator\ErrorElement $errorElement, $object)
{
parent::validate($errorElement, $object);
$errorElement->with('name')
->assertLength(['max' => 100])
;
}
Current display
Expected to get ride of this 32 max length on name's field
Thx for helping
It looks like what you need to do instead, is override this validation config: https://github.com/sonata-project/SonataClassificationBundle/blob/3.x/src/Resources/config/validation.xml
<class name="Sonata\ClassificationBundle\Model\Category">
<property name="name">
<constraint name="NotBlank"/>
<constraint name="Length">
<option name="min">2</option>
<option name="max">32</option>
</constraint>
</property>
</class>

Adding a search box to the homepage of a website built on Sylius

Could someone please explain how I can add a search box to the homepage of my website built on Sylius?
I have tried using the SyliusSearchBundle but that seems to be very out of data and the sample config settings throw errors.
I'd be grateful for any help.
Disclaimer
Steps below work at the time I'm writing. SyliusElasticSearchBundle plugin is in a heavy development state, some of the things crash now, but will be fixed for sure.
I've created a demo repository here: https://github.com/mheki/sylius-search-demo
You need running ElasticSearch and a SyliusElasticSearchBundle plugin.
Follow installation instructions from readme: https://github.com/Lakion/SyliusElasticSearchBundle
Just bear in mind to install sylius dev-master (circular dependency...)
Import bundle's config, routing, enable it in AppKernel.
At the moment I'm writing the bundle needed filter_sets config, otherwise it crashed
So start with the simple search by product name:
lakion_sylius_elastic_search:
filter_sets:
default:
filters:
name:
type: string
Populate elastic index with:
bin/console fos:elastic:pop
Override original routing for lakion_elastic_search_shop_product_index - use filter_set: for your channel code.
lakion_elastic_search_shop_product_index:
path: /products
methods: [GET]
defaults:
_controller: lakion_sylius_elastic_search.controller.search:filterAction
_sylius:
template: "#LakionSyliusElasticSearch/Product/index.html.twig"
resource_class: "%sylius.model.product.class%"
filter_set: default
requirements:
slug: .+
Original product index page was failing for me and I had to remove
{{ form_row(form.search) }}
from it. So copied #LakionSyliusElasticSearch/Product/index.html.twig into Resources directory:
Resources\LakionSyliusElasticSearchBundle\views\Product\index.html.twig and made that change.
Now the last thing is to create a form, for example copying the file _security.html.twig from SyliusShopBundle. Add something like this:
<div class="item">
<form action="{{ path('lakion_elastic_search_shop_product_index') }}" method="get">
<div class="ui icon input">
<input type="text" placeholder="{{ 'sylius.ui.search'|trans }}..." name="filter_set[name]" />
<button type="submit" class="ui button mini">
<i class="search icon"></i>
</button>
</div>
</form>
</div>
and here we go :)
Alternatively, if you don't want to use the SyliusElasticSearchBundle plugin, you can implement a simple search on your own (which search inside the product name, description and category name) :
Create a classic html search form, for example:
<form role="search" method="get" action="{{path('app_search_results') }}">
<input type="search" id="site-search" name="q"
placeholder="{{ 'walrus.search.input.placeholder'|trans }}"
aria-label="{{ 'walrus.search.aria.label'|trans }}">
<button>{{ 'walrus.search.aria.button'|trans }}</button>
</form>
Create a controller action for the route 'app_search_results' in order to get the search request sent by your search form :
<?php
namespace AppBundle\Controller;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use \Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class SearchController extends Controller
{
/**
* #var LocaleContextInterface
*/
private $locale;
/**
* #var ChannelContextInterface
*/
private $channelContext;
/**
* #var RepositoryInterface
*/
private $productRepository;
public function __construct(
LocaleContextInterface $locale,
ChannelContextInterface $channelContext,
RepositoryInterface $productRepository
)
{
$this->locale = $locale;
$this->channelContext = $channelContext;
$this->productRepository = $productRepository;
}
/**
* #Route("/search", name="app_search_results")
*/
public function searchAction(Request $request) : Response
{
$searchTerm = $request->query->get('q');
$channel = $this->channelContext->getChannel();
$localeCode = $this->locale->getLocaleCode();
$products = $this->productRepository->findByTerm($channel, $localeCode, $searchTerm);
return $this->render(
'#App/search/searchListProducts.html.twig',
[
'products' => $products
]
);
}
}
Don't forget to manually wire the dependency injected through the constructor.
Override the product repository in order to implements the findByTerm() method :
<?php
namespace AppBundle\Repository;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ProductRepository as
BaseProductRepository;
use Sylius\Component\Core\Model\ChannelInterface;
class ProductRepository extends BaseProductRepository
{
public function findByTerm(ChannelInterface $channel, string $locale, $searchTerm): array
{
$qb = $this->createQueryBuilder('p')
->addSelect('translation')
// get the translated product for the product regarding the current locale
->innerJoin('p.translations', 'translation', 'WITH', 'translation.locale = :locale')
->orWhere('translation.name LIKE :searchTerm')
->orWhere('translation.description LIKE :searchTerm')
// get the taxons of the product
->innerJoin('p.productTaxons', 'productTaxon')
->innerJoin('productTaxon.taxon', 'taxon')
// get the translated taxon
->innerJoin('taxon.translations', 'taxonTranslation', 'WITH', 'taxonTranslation.locale = :locale')
->orWhere('taxonTranslation.name LIKE :searchTerm')
->andWhere(':channel MEMBER OF p.channels')
->andWhere('p.enabled = true')
->setParameter('searchTerm', '%'.$searchTerm.'%')
->setParameter('locale', $locale)
->setParameter('channel', $channel)
->getQuery();
return $qb->getResult();
}
}
You just need now to create the page rendering the list of product (the #App/search/searchListProducts.html.twig). And to tell Sylius to use your custom HomepageController instead of the original one. Same goes for your Product Repository. This can be done inside the services.yml file of you app folder :
services:
sylius.controller.shop.homepage:
public: true
class: AppBundle\Controller\Shop\HomepageController
arguments:
- '#templating'
- '#sylius.context.locale'
- '#sylius.repository.taxon'
sylius_product:
resources:
product:
classes:
repository: AppBundle\Repository\ProductRepository

How to translate labels in symfony forms generated with doctrine:crud

There Symfony project. He has Entity/Record.php With the command
php bin/console generate:doctrine:crud --entity=AppBundle:Record
Create a controller and a template. It seems to be nothing but one of the templates generated to create a new recording.
{{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" value="Create" />
{{ form_end(form) }}
Create a form in the controller
$record = new Record();
$form = $this->createForm('AppBundle\Form\RecordType', $record);
Create a form in Form/RecordType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('surname')
->add('patronymic')
->add('dOB', BirthdayType::class)
->add('phone')
;
}
The form of work, all input fields are there, but the name in front of input fields, both logical and should have be generated - it attribute Entity object. But they are in English, which is inconvenient for the user (see. Screenshot). How do I display them in Russian?
In your RecordType add
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'translation_domain' => 'forms'
)
);
}
and then create forms.ru.yml in app/Resources/translations/ clear cache so new translation file is detected (important) and you can write translations by
# forms.ru.yml
Name: Имя
Surname: Фамилия
Phone: Телефон
Above I assume your locale is set to ru if it's not then you will need to adjust file name.

Pass entity in a hidden form field type

I have been trying to setup a hidden custom field type and a transformer like in this example: https://gist.github.com/bjo3rnf/4061232
What I am trying to accomplish is to pass an entity trough a hidden element, that entity should map the form´s entity but for some reason when it get to the controller it get empty.
Can some one help me on how can I pass an entity trough a form?
Thank you
The solution with the custom type and transformer is probably better, but if you just want a quick hack, you can set your field up as an entity type in the formbuilder
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('yourField', 'entity', array('class' => 'AppBundle\Entity\YourEntity', 'read_only' => true));
}
and then do something like this in your template:
<input type="hidden" id="{{ form.yourField.vars.id }}"
name="{{ form.yourField.vars.full_name }}"
value="{{ form.yourField.vars.value }}" />
{% do form.yourField.setRendered %}
Note that this has to be at the top of your form, before you invoke form_widget()

Disable default constraints defined in FOSUserBundle

I'm currently working on a Symfony2 project which uses Sonata.
Previous developers have made modifications inside the vendors directly which is obviously a huge mistake so I'm in the process of refactoring this. I'm currently stuck with some modifications that have been made to constraint mapping from the FOSUserBundle.
The file is: /vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/validation/orm.xml
They actually disabled the UniqueEntity constraint on the class FOS\UserBundle\Model\User based on the field usernameCanonical, like this:
<?xml version="1.0" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="FOS\UserBundle\Model\User">
<!-- <constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">usernameCanonical</option>
<option name="errorPath">username</option>
<option name="message">fos_user.username.already_used</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint> -->
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">emailCanonical</option>
<option name="errorPath">email</option>
<option name="message">fos_user.email.already_used</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint>
</class>
</constraint-mapping>
How can I reproduce this change in the overriden FOSUserBundle, or the overriden SonataUserBundle ?
I have a solution, but don't know if it will fit your needs.
You can change de validation groups name in your config.yml for fos_user.
For example :
fos_user:
...
registration:
...
form:
validation_groups: [YourBundleNameRegistration, Default] #before it was [Registration, Default]
profile:
...
form:
...
validation_groups: [YourBundleNameProfile, Default] #before it was [Profile, Default]
Then the validation constraints defined in /vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/validation/orm.xml will not applied anymore.
You have to copy the original orm.xml, and paste it in your YourBundle/Resources/config directory. And in this copy you replace Registration and Profile by YourBundleNameRegistration and YourBundleNameProfile. And then you remove the unique constraint on usernameCanonical.
As much as the answer from #Nico pointed me to the right direction, here is the complete setup I've had to do in order to integrate it into the SonataUserBundle and have my custom validation groups taken into account.
As a matter of fact, it seemed that my custom orm.xml file was not loaded. So after changing the validation groups, no more validation occurred at all.
1. Create a file orm.xml
In Application\Sonata\UserBundle\Resources\config\validation\:
<?xml version="1.0" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
<class name="FOS\UserBundle\Model\User">
<!-- <constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">usernameCanonical</option>
<option name="errorPath">username</option>
<option name="message">fos_user.username.already_used</option>
<option name="groups">
<value>Registration</value>
<value>Profile</value>
</option>
</constraint> -->
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">emailCanonical</option>
<option name="errorPath">email</option>
<option name="message">fos_user.email.already_used</option>
<option name="groups">
<value>CustomRegistration</value>
<value>CustomProfile</value>
</option>
</constraint>
</class>
</constraint-mapping>
2. Tell FOSUserBundle and SonataUserBundle
To use the custom validation groups (I've stripped down the non-relevant configuration lines):
app/config/fos/fos_user.yml:
fos_user:
registration:
form:
validation_groups:
- CustomRegistration
profile:
form:
validation_groups:
- CustomProfile
app/config/sonata/sonata_user.yml:
sonata_user:
profile:
form:
validation_groups:
- CustomProfile
3. Tell the compiler to load the "overriden" validation file
Add Application\Sonata\UserBundle\DependencyInjection\Compiler\ValidationPass.php class. This is based on the original class from FOSUserBundle:
class ValidationPass implements CompilerPassInterface
{
/**
* {#inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasParameter('fos_user.storage')) {
return;
}
$storage = $container->getParameter('fos_user.storage');
if ('custom' === $storage) {
return;
}
$validationFile = __DIR__ . '/../../Resources/config/validation/' . $storage . '.xml';
if ($container->hasDefinition('validator.builder')) {
// Symfony 2.5+
$container->getDefinition('validator.builder')
->addMethodCall('addXmlMapping', array($validationFile));
return;
}
}
}
Edit Application\Sonata\UserBundle\ApplicationSonataUserBundle:
class ApplicationSonataUserBundle extends Bundle
{
/**
* {#inheritdoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ValidationPass());
}
}
4. Override the default validation groups in the UserAdmin class of Sonata (within an overloaded UserAdmin class)
/**
* {#inheritdoc}
*/
public function getFormBuilder()
{
$this->formOptions['data_class'] = $this->getClass();
$options = $this->formOptions;
$options['validation_groups'] =
(!$this->getSubject() || is_null($this->getSubject()->getId())) ? 'CustomRegistration' : 'CustomProfile';
$formBuilder = $this->getFormContractor()->getFormBuilder( $this->getUniqid(), $options);
$this->defineFormBuilder($formBuilder);
return $formBuilder;
}
I don't know if there was a simpler way to achieve this. Nevertheless, it works for me.

Resources