I've created a custom form validator that does not seem to be working. It gets called during form submission and returns false when it should fail validation. But it doesn't seem to tell the form that validation has failed.
Here is the form validator: (code box scrolls)
<?php
namespace Redacted\AppBundle\Validator\Constraints;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RequiredIfPlainPasswordSetValidator extends ConstraintValidator
{
/**
* RequestStack instance.
*
* #var Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* dependency injection.
*
* #param Symfony\Component\HttpFoundation\RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* if the plain password has been set, the current password must not be
* empty.
*
* #param string $currentPassword
* #param Symfony\Component\Validator\Constraint $constraint
*
* #return bool
*/
public function validate($currentPassword, Constraint $constraint)
{
$plainPassword = $this->requestStack->getCurrentRequest()->request
->get('security_settings')['plainPassword']['first'];
if ($plainPassword && !$currentPassword) {
return false;
}
return true;
}
}
And here is the constraint that goes with it:
<?php
namespace Redacted\AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class RequiredIfPlainPasswordSet extends Constraint
{
/**
* error message
*
* #var string
*/
protected $message = 'Please enter your current password.';
/**
* the alias for the related validator in services.yml
*
* #return string
*/
public function validatedBy()
{
return 'required_if_plain_password_set';
}
}
and the relevant part of app/config/services.yml:
services:
redacted.appbundle.required_if_plain_password_set:
class: Redacted\AppBundle\Validator\Constraints\RequiredIfPlainPasswordSetValidator
arguments: ["#request_stack"]
tags:
- { name: validator.constraint_validator, alias: required_if_plain_password_set }
I've created a custom form type: (code box scrolls)
<?php
namespace Redacted\AppBundle\Form;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
class SecuritySettingsFormType extends AbstractType
{
/**
* the name of the form type
*
* #return string
*/
public function getName()
{
return 'security_settings';
}
/**
* add fields to form
*
* #param Symfony\Component\Form\FormBuilderInterface $formBuilder
* #param array $options
*/
public function buildForm(FormBuilderInterface $formBuilder, array $options)
{
$formBuilder
->add('email')
->add('currentPassword', 'password')
->add('plainPassword', 'repeated', [
'type' => 'password',
'invalid_message' => 'Passwords must match.',
]);
}
/**
* configureOptions.
*
* #param Symfony\Component\OptionsResolver\OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$options = [
'data_class' => 'AppBundle\Entity\User',
'validation_groups' => ['security_settings'],
];
$resolver->setDefaults($options);
}
}
which validates against the User entity due to the data_type. Here is the relevant part of the User entity:
<?php
namespace Redacted\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Redacted\AppBundle\Validator\Constraints\RequiredIfPlainPasswordSet;
class User implements UserInterface, \Serializable
{
// ...
/**
* #var string (default: null)
*
* #Assert\NotBlank(message="user.email_required", groups={"security_settings"})
* #Assert\Email(message="security_settings.valid_email" groups={"security_settings"})
* #ORM\Column(name="email", type="string", length=255, nullable=true, unique=true)
*/
private $email = null;
/**
* current password - used for validation only
*
* #RequiredIfPlainPasswordSet(
* message="security_settings.current_password_required",
* groups={"security_settings"}
* )
* #var string
*/
protected $currentPassword;
// ... setters and getters for above, etc.
}
And finally, here's the controller method I'm using to check. I'll leave out the view because it's probably irrelevant. (I'm defining my controller as a service so no redirectToRoute(), etc.)
// in the controller class...
/**
* Security settings page - email, password, etc.
*
* #Route("/security-settings", name="security_settings")
* #Template()
*
* #param Symfony\Component\HttpFoundation\Request $request
*
* #return array|Symfony\Component\HttpFoundation\RedirectResponse
*/
public function securitySettingsAction(Request $request)
{
$loggedInUser = $this->tokenStorage->getToken()->getUser();
$form = $this->formFactory
->create(new SecuritySettingsFormType(), $loggedInUser);
$form->handleRequest($request);
if ($form->isValid()) {
$user = $form->getData();
// persist the user
$this->saveUserSettings($user);
// set a success message
$this->setSecuritySettingsFlashSuccess($request);
// redirect back
$route = $this->router->generate('security_settings');
return new RedirectResponse($route);
}
return ['form' => $form->createView()];
}
The idea is that the current password should only be required if the new password is entered. Although that validate() function is getting called and returning false when it should, the form's isValid() is returning true and it's saving. If I add a #Assert\NotBlank(groups={"security_settings"}) assertion to the User::currentPassword field, it does fire and fail successfully, so it is looking for validation annotations on that field.
What am I missing?
The problem was that the validate() method of the ConstraintValidator should not just return true or false, it should build a violation like so:
/**
* if the plain password has been set, the current password must not be
* empty.
*
* #param string $currentPassword
* #param Symfony\Component\Validator\Constraint $constraint
*/
public function validate($currentPassword, Constraint $constraint)
{
$plainPassword = $this->requestStack->getCurrentRequest()->request
->get('security_settings')['plainPassword']['first'];
if ($plainPassword && !$currentPassword) {
$this->context->buildViolation($constraint->message)
->atPath('currentPassword')->addViolation();
}
}
Thanks to my coworker for catching it. All works as expected now.
Related
I have a form where a user enters his phone number. A common problem is that a phone number can be written in many different ways: "+49 711 XXXXXX", "0049 (0)711 XXXXXX" or "+49 711 - XXXXXX" are all presentations of the same phone number. In order to detect duplicates I use the "phone-number-bundle" (https://github.com/misd-service-development/phone-number-bundle) to get a "normalized" E.164 representation of the phone number that can be used for comparison. If a duplicate is detected, the entered number must not be stored and a notice has to be shown to the user.
If the entered phone number is a valid phone number, I want to check if the E.164-formatted value of the phone number is already stored in the database table.
This is the MySQL table for phone numbers:
-+----+---------------------+----------------+
| id | original | phonenumber |
-+----+---------------------+----------------+
| 1 | 0711-xxxxxxx | +49711xxxxxxx |
-+----+---------------------+----------------+
| 2 | +49 7034 / xxxxx-xx | +497034xxxxxxx |
-+----+---------------------+----------------+
| 3 | +49 (0)171/xxxxxxx | +49171xxxxxxx |
-+----+---------------------+----------------+
| .. | ... | ... |
-+----+---------------------+----------------+
"phonenumber" contains the E.164 formatted value of the value entered in the form. The first originally entered value is stored in the column "original" as additional information.
The form is defined in "src/AppBundle/Form/PhonenumberType.php":
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use libphonenumber\PhoneNumberFormat;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PhonenumberType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//->add('phonenumber') // Remove comments to see that the unique constraint works when the phonenumber is submitted via form
->add('original')
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Phonenumber'
));
}
}
Phonenumber Entity "src/AppBundle/Entity/Phonenumber.php":
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Validator\Constraints as PhonenumberAssert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Phonenumber
*
* #ORM\Table(name="phonenumber",
* uniqueConstraints={
* #ORM\UniqueConstraint(columns={"phonenumber"})
* })
* #ORM\Entity
* #UniqueEntity("phonenumber")
* #ORM\HasLifecycleCallbacks()
*/
class Phonenumber
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="phonenumber", type="string", length=255, unique=true)
*/
private $phonenumber;
/**
* #var string
*
* #ORM\Column(name="original", type="string", length=255, precision=0, scale=0, nullable=true, unique=false)
* #Assert\NotBlank
* #PhonenumberAssert\IsValidPhoneNumber
*/
private $original;
/**
* Constructor
*/
public function __construct()
{
}
/**
* Returns phonenumber.
*
* #return string
*/
public function __toString()
{
return $this->getPhonenumber();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set phonenumber
*
* #param string $phonenumber
*
* #return Phonenumber
*/
public function setPhonenumber($phonenumber)
{
$this->phonenumber = $phonenumber;
return $this;
}
/**
* Get phonenumber
*
* #return string
*/
public function getPhonenumber()
{
return $this->phonenumber;
}
/**
* Set original
*
* #param string $original
*
* #return Phonenumber
*/
public function setOriginal($original)
{
$this->original = $original;
return $this;
}
/**
* Get original
*
* #return string
*/
public function getOriginal()
{
return $this->original;
}
}
Defined services in "app/config/services.yml":
services:
phonenumber_validation:
class: AppBundle\Validator\Constraints\IsValidPhoneNumberValidator
arguments: ["#service_container"]
tags:
- { name: validator.constraint_validator, alias: phonenumber_validation }
my.subscriber:
class: AppBundle\EventListener\PhoneNumberNormalizerSubscriber
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: doctrine.event_subscriber, connection: default }
The subscriber class "src/AppBundle/EventListener/PhoneNumberNormalizerSubscriber.php":
<?php
namespace AppBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use AppBundle\Entity\Phonenumber;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
class PhoneNumberNormalizerSubscriber implements EventSubscriber
{
/** #var ContainerInterface */
protected $container;
/**
* #param ContainerInterface #container
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
}
public function getSubscribedEvents()
{
return array(
'prePersist',
'preUpdate',
);
}
// Executed when data is stored for the first time
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
// only act on "Phonenumber" entity
if ($entity instanceof Phonenumber)
{
$entityManager = $args->getEntityManager();
$phoneNumberObj = $this->container->get('libphonenumber.phone_number_util')->parse($entity->getOriginal(), 'DE');
$normalized_phonenumber = $this->container->get('libphonenumber.phone_number_util')->format($phoneNumberObj, PhoneNumberFormat::E164);
$entity->setPhonenumber($normalized_phonenumber);
}
}
// Executed when data is already stored
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
// only act on "Phonenumber" entity
if ($entity instanceof Phonenumber)
{
$entityManager = $args->getEntityManager();
$phoneNumberObj = $this->container->get('libphonenumber.phone_number_util')->parse($entity->getOriginal(), 'DE');
$normalized_phonenumber = $this->container->get('libphonenumber.phone_number_util')->format($phoneNumberObj, PhoneNumberFormat::E164);
$entity->setPhonenumber($normalized_phonenumber);
}
}
}
The constraint class "src/AppBundle/Validator/Constraints/IsValidPhoneNumber.php":
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class IsValidPhoneNumber extends Constraint
{
public $message_invalid = 'Not a valid phone number: "%string%"';
/**
* #return string
*/
public function validatedBy()
{
return 'phonenumber_validation';
}
}
The validator class "src/AppBundle/Validator/Constraints/IsValidPhoneNumberValidator.php":
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
class IsValidPhoneNumberValidator extends ConstraintValidator
{
private $container;
/**
* Construct
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Validate
*
* #param mixed $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
if ($value != '' )
{
$phoneNumberObj = $this->container->get('libphonenumber.phone_number_util')->parse($value, 'DE');
if (!$this->container->get('libphonenumber.phone_number_util')->isValidNumber($phoneNumberObj))
{
$this->context->buildViolation($constraint->message_invalid)
->setParameter('%string%', $value)
->addViolation();
}
}
}
}
The phone number validator works - if a number is invalidated by the "phone-number-bundle", a message "Not a valid phone number: "3333333333333333" is displayed. The E.164 formatted value gets also saved correctly into the database table.
Problem: Although I use "#ORM\UniqueConstraint(columns={"phonenumber"})", "#UniqueEntity("phonenumber")" and "unique=true" for the $phonenumber attribute in the entity class, every entered valid number from the form gets stored in the database, no matter if there is already a duplicate in the table or not. The unique constraint does not work when the phonenumber field is not added in the form type class.
May be interesting: When I remove the comment in the PhonenumberType class so that
->add('phonenumber')
is included again and an existing number is entered in the associated form field "phonenumber", I get "This value is already used." like expected.
What am I doing wrong?
Thanks for helping!
Indeed, it is not.
You have to differentiate:
Form validation. It occurs when your controller calls $form->handle($request) or something similar, triggered on a form event
Doctrine callbacks, that are called during the EntityManager flush()
The solution is then to use that bundle not on prePersist(), but rather on a Form event. Data will be normalized before writing it to the entity, then effectively validating what you will save to DB.
Such a code would look like this:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use libphonenumber\PhoneNumberFormat;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class PhonenumberType extends AbstractType
{
private $phoneNumberUtil;
public function __construct(PhoneNumberUtil $phoneNumberUtil)
{
$this->phoneNumberUtil = $phoneNumberUtil;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('phonenumber', 'hidden')
->add('original')
->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) use ($this) {
$entity = $event->getData();
$phoneNumber = $this->phoneNumberUtil->parse($entity->getOriginal(), PhoneNumberUtil::UNKNOWN_REGION);
$normalized_phonenumber = $this->phoneNumberUtil->format($phoneNumberObj, PhoneNumberFormat::E164);
$entity->setPhoneNumber($normalized_phonenumber);
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Phonenumber'
));
}
}
One more thing, declaring your form type as a service will make the build simplfer, have a look there: http://symfony.com/doc/current/book/forms.html#defining-your-forms-as-services
Edit, here is the YML service definition:
app.contact_type:
class: AppBundle\Form\PhonenumberType
arguments:
- #libphonenumber.phone_number_util
tags:
- { name: form.type, alias: 'phone_number' }
I have a lot of Categories in database.
Here is Category Entity
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Category")
*/
protected $rootCategory;
/**
* #ORM\Column(type="text")
*/
protected $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set rootCategory
*
* #param \AppBundle\Entity\Category $rootCategory
*
* #return Category
*/
public function setRootCategory(\AppBundle\Entity\Category $rootCategory = null)
{
$this->rootCategory = $rootCategory;
return $this;
}
/**
* Get rootCategory
*
* #return \AppBundle\Entity\Category
*/
public function getRootCategory()
{
return $this->rootCategory;
}
}
I want to get all categories in my edit form
EditFormType:
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use AppBundle\Controller\CategoryController;
class EditPhotoFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$categoryController = new CategoryController();
$builder->add('title', 'text');
$builder->add('description', 'textarea');
$builder->add('category', EntityType::class, array(
'class' => 'AppBundle:Category',
'choices' => $categoryController->getCategories(),
));
}
public function getName()
{
return 'app_photo_edit';
}
}
getCategories()
public function getCategories() {
$em = $this->getDoctrine()->getManager();
return $em->getRepository('AppBundle:Category')->findAll();
}
I am getting next error:
Error: Call to a member function has() on null
Thats because there is not Doctrine in controller object. Where should i get Doctrine and Repository in this case?
How should i do it correct way?
First, you should NEVER instantiate any Controller class yourself. Controller classes are used by Symfony's Kernel to handle a request, and they are loaded automatically with dependencies to do so.
Right here, you don't even need to require the EntityManager in your FormType, because EntityType has a built-in option query_builder to do what you need:
$builder->add('category', EntityType::class, array(
'class' => 'AppBundle:Category',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c');
},
);
This should do the trick. (check here for more details)
However, if one day you really need to import a dependancy inside your Form (whether it is EntityManager or another service), here's how you should do:
A. import the given dependency in your constructor:
private $dependency;
public function __construct(Dependency $dependency)
{
$this->$dependency = $dependency;
}
B. Declare your Form as a Service, with your dependency's id as argument:
<service id="app.form.type.edit_photo"
class="AppBundle\Form\Type\EditPhotoFormType">
<tag name="form.type" />
<argument type="service" id="app.dependencies.your_dependency" />
</service>
Then use $this->dependency in your Form wherever you need.
Hope this helps! :)
I am building an app based on Sylius standard edition. With the ResourceBundle i handled to integrate my own entities and the corresponding relations. This new resources should be related later to the products entity, but first i want to get it working "standalone". The backend works for both, the added resource and for relations. These are editable via form-collections. Very fine! Now i want to get translated database-content for my new resource. I tried the way i did it in Symfony earlier, but it didn't work. The last vew days i tried every possible solution found, but none of this works, or i made mistakes... Neither the translation-tables where constructed when typing:
app/console doctrine:schema:update --force
nor translatable content is visible in the forms. When calling the edit action, i get following error:
error:
Neither the property "translations" nor one of the methods "getTranslations()", "translations()", "isTranslations()", "hasTranslations()", "__get()" exist and have public access in class "KontaktBundle\Entity\Kontakte".
Is somebody out there with an example implementation of a extended Sylius-resource with translateable database-content? I'm still learning symfony and sylius too, can you tell me what i'm missing or doing wrong?
Many Thanks to #gvf. Now i figured out that the config entry must be set. This gave me a functional example which i want to provide here:
Configs
# app/config/sylius_config.yml (must be imported in config)
# Adding Resource
sylius_resource:
# Resource Settings
settings:
sortable: true
paginate: 50
allowed_paginate: [50, 100, 500]
filterable: true
resources:
dbk.authors:
driver: doctrine/orm
templates: AuthorBundle:Backend
object_manager: default
classes:
model: AuthorBundle\Entity\Kontakte
#interface: // if you have an interface configured
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
translation:
model: AuthorBundle\Entity\AuthorsTranslation
mapping:
fields: ['anrede','biografie']
Services
# app/config/sylius_services.yml (must be imported in config)
parameters:
# Parameter for our author entity
app.form.type.authors.class: AuthorBundle\Form\AuthorsType
app.form.type.authors_translation.class: AuthorBundle\Form\AuthorsTranslationType
services:
# Adding Authors Backend menu Item
dbk_backend_authors.menu_builder:
class: AuthorBundle\EventListener\MenuBuilderListener
tags:
- { name: kernel.event_listener, event: sylius.menu_builder.backend.main, method: addBackendMenuItems }
- { name: kernel.event_listener, event: sylius.menu_builder.backend.sidebar, method: addBackendMenuItems }
# Adding Authors FormType
app.form.type.authors:
class: "%app.form.type.authors.class%"
tags:
- {name: form.type, alias: dbk_authors }
app.form.type.authors_translation:
class: "%app.form.type.authors_translation.class%"
tags:
- {name: form.type, alias: dbk_authors_translation }
EventListener (Adds Menu Entry in Sylius-Backend)
<?php
// AuthorBundle/EventListener/MenuBuilderListener/MenuBuilderListener.php
namespace AuthorBundle\EventListener;
use Sylius\Bundle\WebBundle\Event\MenuBuilderEvent;
class MenuBuilderListener
{
public function addBackendMenuItems(MenuBuilderEvent $event)
{
$menu = $event->getMenu();
$menu['assortment']->addChild('vendor', array(
'route' => 'Authors',
'labelAttributes' => array('icon' => 'glyphicon glyphicon-user'),
))->setLabel('Authors');
}
}
Entities
Authors (The new resource hich we want to add and to translate)
<?php
// AuthorBundle/Entity/Authors.php
namespace AuthorBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Translation\Model\AbstractTranslatable;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Authors
*
* #ORM\Entity
* #ORM\Table(name="authors")
*/
class Authors extends AbstractTranslatable
{
//
// IDENTIFIER FIELDS
//
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", length=20, nullable=false, options={"unsigned":"true"})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//
// FIELDS
//
/**
* #var string
*
* #ORM\Column(name="vorname", type="string", length=255)
*/
private $vorname;
/**
* #var string
*
* #ORM\Column(name="nachname", type="string", length=255)
*/
private $nachname;
public function __construct() {
parent::__construct();
}
//
// TranslationFields - Getters and Setters
//
/**
* Get Anrede
* #return string
*/
public function getAnrede()
{
return $this->translate()->getAnrede();
}
/**
* Set Anrede
*
* #param string $anrede
*
* #return Authors
*/
public function setAnrede($anrede)
{
$this->translate()->setAnrede($anrede);
return $this;
}
/**
* Get Biografie
* #return string
*/
public function getBiografie()
{
return $this->translate()->getBiografie();
}
/**
* Set Biografie
*
* #param string $biografie
*
* #return Authors
*/
public function setBiografie($biografie)
{
$this->translate()->setBiografie($biografie);
return $this;
}
//
// Getters and Setters
//
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set vorname
*
* #param string $vorname
*
* #return Authors
*/
public function setVorname($vorname)
{
$this->vorname = $vorname;
return $this;
}
/**
* Get vorname
*
* #return string
*/
public function getVorname()
{
return $this->vorname;
}
/**
* Set nachname
*
* #param string $nachname
*
* #return Authors
*/
public function setNachname($nachname)
{
$this->nachname = $nachname;
return $this;
}
/**
* Get nachname
*
* #return string
*/
public function getNachname()
{
return $this->nachname;
}
public function __toString(){
return $this->getFullName();
}
}
AuthorsTranslation (This is the Entity for tanslation of Authors)
<?php
// AuthorBundle/Entity/AuthorsTranslation.php
namespace AuthorBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Translation\Model\AbstractTranslation;
/**
* AuthorsTranslation
*
* #ORM\Entity
* #ORM\Table(name="authors_translation")
*/
class AuthorsTranslation extends AbstractTranslation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", length=20, nullable=false, options={"unsigned":"true"})
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//
// TRANSLATABLE - FIELDS
//
/**
* #var string
* #ORM\Column(name="anrede", type="string", length=255)
*/
private $anrede;
/**
* #var string
* #ORM\Column(name="biografie", type="text")
*/
private $biografie;
/**
* {#inheritdoc}
*/
public function getId()
{
return $this->id;
}
//
// GETTERS AND SETTERS
//
/**
* Set anrede
*
* #param string $anrede
* #return Authors
*/
public function setAnrede($anrede)
{
$this->anrede = $anrede;
return $this;
}
/**
* Get anrede
*
* #return string
*/
public function getAnrede()
{
return $this->anrede;
}
/**
* Set biografie
*
* #param string $biografie
*
* #return Authors
*/
public function setBiografie($biografie)
{
$this->biografie = $biografie;
return $this;
}
/**
* Get biografie
*
* #return string
*/
public function getBiografie()
{
return $this->biografie;
}
}
FormTypes
AuthorsType
<?php
// AuthorBundle/Form/AuthorsType.php
namespace AuthorBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\QueryBuilder;
class AuthorsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Add Translations to Form.
->add('translations', 'a2lix_translations', array(
'required' => false,
'fields' => array(
'anrede' => array(
'label' => 'Anrede',
),
'biografie' => array(
'label' => 'Biografie',
'attr' => array('data-edit' => 'wysiwyg', 'rows' => '15'),
'required' => false,
)
)
))
->add('vorname', null, array(
'label' => 'Vorname',
'required' => false,
))
->add('nachname', null, array(
'label' => 'Nachname',
'required' => false,
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
'data_class' => 'AuthorBundle\Entity\Authors'
));
}
/**
* #return string
*/
public function getName()
{
return 'dbk_authors';
}
}
AuthorsTranslationType
<?php
// AuthorBundle/Form/AuthorsTranslationType.php
namespace AuthorBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorsTranslationType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('anrede', 'text', array(
'label' => 'Anrede'
))
->add('biografie', 'textarea', array(
'label' => 'Biografie'
))
;
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'dbk_authors_translation';
}
}
Form Template
{# AuthorBundle/Resources/views/backend/edit||add.html.twig - as you like... the call of form.translation (line 5) is the point here #}
{% form_theme form 'SyliusWebBundle:Backend:forms.html.twig' %}
<div class="row">
<div class="col-md-12">
{{ form_row(form.translations, {'attr': {'class': 'input-lg'}}) }}
{{ form_row(form.vorname) }}
{{ form_row(form.nachname) }}
</div>
</div>
Have a look at the Product and ProductTranslation under the models folder in Sylius Components to see an example of how Sylius implements it.
Kontakte needs to extend AbstractTranslatable, you also need to create a class KontakteTranslation that extends AbstractTranslation. Under sylius_resource you also need to configure the translations:
sylius_resource:
resources:
dbk.contact:
driver: doctrine/orm
templates: KontaktBundle:Backend
object_manager: default
classes:
model: KontaktBundle\Entity\Kontakte
controller: Sylius\Bundle\ResourceBundle\Controller\ResourceController
repository: Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository
translation:
model: KontaktBundle\Entity\KontakteTranslation
mapping:
fields: {...fields that will be translated...}
Get rid of gedmo translatable extension because Sylius doesn't use it.
I've been stuck on this "Target not instantiable" error for the last 2-3 days and I have no idea why. Someone in the IRC #laravel chat room, yesterday, suggested using var_dump(App::make('Project\Frontend\Repo\Lead\LeadInterface')) when in Artisan's tinker interface which I've done and got this response:
class Project\Frontend\Repo\Lead\EloquentLead#798 (1) {
protected $lead =>
class Lead#809 (21) {
// bunch of info about the model etc relating to the interface and it's repo class
}
}
One of the members in #laravel pointed out that this meant the interface was instantiated which is great but then I realised this isn't the interface that I'm having a problem with.
So below is how I have things setup. The interface that's apparently not instantiated is ValidableInterface (last portion of code) and running var_dump(App::make('Project\Backend\Service\Validation\ValidableInterface'))
returns the dreaded "Target not instantiable" error.
EDIT: new LeadFormLaravelValidator( $app['validator'] ) extends AbstractLaravelValidator which implements ValidableInterface.
Am I missing some glaring problem with my code?
My service provider
<?php namespace Project\Frontend\Service\Form;
use Illuminate\Support\ServiceProvider;
use Project\Frontend\Service\Form\Lead\LeadForm;
use Project\Frontend\Service\Form\Lead\LeadFormLaravelValidator;
class FormServiceProvider extends ServiceProvider {
/**
* Register the binding
*
* #return void
*/
public function register()
{
$app = $this->app;
$app->bind('Project\Frontend\Service\Form\Lead\LeadForm', function($app)
{
return new LeadForm(
new LeadFormLaravelValidator( $app['validator'] ),
$app->make('Project\Frontend\Repo\Lead\LeadInterface')
);
});
}
}
My form class
<?php namespace Project\Frontend\Service\Form\Lead;
use Project\Backend\Service\Validation\ValidableInterface;
use Project\Frontend\Repo\Lead\LeadInterface;
class LeadForm {
/**
* Form Data
*
* #var array
*/
protected $data;
/**
* Validator
*
* #var \Project\Backend\Service\Validation\ValidableInterface
*/
protected $validator;
/**
* Lead repository
*
* #var \Project\Frontend\Repo\Lead\LeadInterface
*/
protected $lead;
public function __construct(ValidableInterface $validator, LeadInterface $lead)
{
$this->validator = $validator;
$this->lead = $lead;
}
My validation rules
<?php namespace Project\Frontend\Service\Form\Lead;
use Project\Backend\Service\Validation\AbstractLaravelValidator;
class LeadFormLaravelValidator extends AbstractLaravelValidator {
/**
* Validation rules
*
* #var Array
*/
protected $rules = array(
'name' => 'required|regex:/^[a-zA-Z-\s]+$/',
'email' => 'email',
'cell' => 'required|numeric|digits_between:10,11',
);
/**
* Validation messages
*
* #var Array
*/
protected $messages = array(
'regex' => 'The :attribute may only contain letters, dashes and spaces.',
'digits_between' => 'The :attribute must be 10 numbers long.',
);
}
My abstract validator
<?php namespace Project\Backend\Service\Validation;
use Illuminate\Validation\Factory;
abstract class AbstractLaravelValidator implements ValidableInterface {
/**
* Validator
*
* #var \Illuminate\Validation\Factory
*/
protected $validator;
/**
* Validation data key => value array
*
* #var Array
*/
protected $data = array();
/**
* Validation errors
*
* #var Array
*/
protected $errors = array();
/**
* Validation rules
*
* #var Array
*/
protected $rules = array();
/**
* Custom validation messages
*
* #var Array
*/
protected $messages = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
/**
* Set data to validate
*
* #return \Project\Backend\Service\Validation\AbstractLaravelValidator
*/
public function with(array $data)
{
$this->data = $data;
return $this;
}
/**
* Validation passes or fails
*
* #return Boolean
*/
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules, $this->messages);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
/**
* Return errors, if any
*
* #return array
*/
public function errors()
{
return $this->errors;
}
}
My validator interface
<?php namespace Project\Backend\Service\Validation;
interface ValidableInterface {
/**
* Add data to validation against
*
* #param array
* #return \Project\Backend\Service\Validation\ValidableInterface $this
*/
public function with(array $input);
/**
* Test if validation passes
*
* #return boolean
*/
public function passes();
/**
* Retrieve validation errors
*
* #return array
*/
public function errors();
}
I believe the problem is $app->make('Project\Frontend\Repo\Lead\LeadInterface'). Laravel has no way of knowing what class to instantiate here. You have to tell Laravel by doing:
$app->bind('Project\Frontend\Repo\Lead\LeadInterface', 'Your\Implementation\Of\LeadInterface');
Edit
It's weird that you get that exception since you manually instantiate LeadForm and inject the LeadFormLaravelValidator. However this should probably resolve the issue:
$app->bind('Project\Backend\Service\Validation\ValidableInterface',
'Project\Frontend\Service\Form\Lead\LeadFormLaravelValidator');
I am having an issue with Validating an EWZ recaptcha field in a form that I have in Symfony2.
Here is my Captcha Entity:
<?php
namespace Acme\FormBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints as Recaptcha;
/**
* Captcha
*
* #ORM\Table()
* #ORM\Entity
*/
class Captcha
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var boolean
*
* #ORM\Column(name="captcha", type="boolean")
*/
private $captcha;
/**
* #var integer
*
* #ORM\Column(name="uniqueid", type="integer")
*/
private $uniqueid;
/**
* #Recaptcha\True
*/
public $recaptcha;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set captcha
*
* #param boolean $captcha
* #return Captcha
*/
public function setCaptcha($captcha)
{
$this->captcha = $captcha;
return $this;
}
/**
* Get captcha
*
* #return boolean
*/
public function getCaptcha()
{
return $this->captcha;
}
/**
* Set uniqueid
*
* #param integer $uniqueid
* #return Captcha
*/
public function setUniqueid($uniqueid)
{
$this->uniqueid = $uniqueid;
return $this;
}
/**
* Get uniqueid
*
* #return integer
*/
public function getUniqueid()
{
return $this->uniqueid;
}
}
Here is my Form:
<?php
namespace Acme\FormBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;
class CaptchaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('recaptcha', 'ewz_recaptcha')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\FormBundle\Entity\Captcha'
));
}
public function getName()
{
return 'captchaType';
}
}
Here is my Controller:
<?php
namespace Acme\FormBundle\Controller;
use Acme\FormBundle\Entity\User;
use Acme\FormBundle\Entity\Workstation;
use Acme\FormBundle\Entity\Captcha;
use Acme\FormBundle\Form\CaptchaType;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Form\FormBuilderInterface;
class DefaultController extends Controller
{
public function indexAction(Request $request)
{
$captcha = new Captcha();
$form = $this->createForm(new CaptchaType(), $captcha);
if ($request->isMethod('POST')) {
echo "1";
$form->bind($request);
echo "2";
if ($form->isValid()) {
echo "3A";
// perform some action, such as saving the task to the database
$session = $this->getRequest()->getSession();
$temptime = strtotime("now");
$session->set('uniqueid', $temptime);
return $this->redirect($this->generateUrl('customer_info'));
}else{
return $this->redirect($this->generateUrl('captcha'));
}
echo "3B";
}
return $this->render('AcmeFormBundle:Default:index.html.twig', array('form' => $form->createView()));
}
When I check to see if the form is valid, it never equals true. I'm sure that I'm missing something pretty easy, but I have been trying to figure this out for the past 4 hours, and I'm stuck.
Any help is greatly appreciated.
Thanks
After a lot Googling, I ended up fixing my problem. I ended up changing my buildform to the following:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('recaptcha', 'ewz_recaptcha', array(
'attr' => array(
'options' => array(
'theme' => 'clean'
)
),
'mapped' => false,
'constraints' => array(
new True()
),
'error_bubbling' => true
));
}
And changed my config.yml to this:
validation: { enable_annotations: false }
That fixed my issue. Kind of wish this was a little more clear in the installation instructions.
Oh well.