This is the common porblem.
There is the form with many checkbocks. Making the checkbocks are aviable and click save, fields corresponding checkbock-labels don't save.
PersonAdmin class contains
...
->add('books', 'sonata_type_model',
array('by_reference' => false, 'expanded' => true, 'multiple' => true, 'label' => 'Books'))
...
Entity class contains
/**
* #ORM\ManyToMany(targetEntity="Book", mappedBy="persons", cascade={"persist"})
* #ORM\JoinTable(name="person_book")
*/
protected $books;
....
public function __construct()
{
$this->books = new ArrayCollection();
}
public function addBook(Book $book)
{
$this->books[] = $book;
return $this;
}
and geters, seters...
I unsuccessfully searched for a solution. I have found that it is necessary to add
'by_reference' => false,
or
cascade={"persist"}
but I have all of this in my code.
As mentioned before you need to save relation in both sides. But I would prefer another way: to save relation in the add actions of your entities:
//In the Person entity:
public function addBook(Book $book)
{
$book->addPerson($this);
$this->books[] = $book;
return $this;
}
//In the Book entity (if you have the same problem for another side):
public function addPerson(Person $person)
{
$person->addBook($this);
$this->persons[] = $person;
return $this;
}
You have to save at both sides.
Override the default editAction and createAction in a custom crud controller.
e.g.:
It's an example of a many to many relationship between artists and events.
($object is the current object you are editing/creating in the action)
foreach ($form['selectArtists']->getData() as $key => $value) {
$artist = $em->getRepository('MyCompanyProjectBundle:Artist')->findOneById($value);
$object->addArtist($artist);
$artist->addEvent($object);
$em->persist($artist);
}
Use edit=>inline .
->add('books', 'sonata_type_collection',
array('by_reference' => false, 'label' => 'Books'),
array('edit'=>'inline','inline'=>'table'))
May be help you.
Related
I've extended Symfony's EntityType as UserChooserType for use with my User entity and Select2. The choice list for the UserChooserType comes from an ldap query (via an ajax call), not a Doctrine query. So the field starts out blank.
The User entity is related to many different entities across my application. But if I want the UserChooserType to load with a current the selected User I have to add a listener to every form that uses it. e.g.:
class SiteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$siteAdminOpts = array('label' => 'entity.site.admin', 'required'=>false);
//opts for the UserChooserType
$builder
->add('siteName', FT\TextType::class, array('label' => 'entity.site.name'))
->add('siteAdmin', UserChooserType::class, $siteAdminOpts )
//must be added to every form type that uses UserChooserType with mod for the datatype that $event->getData() returns
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$site = $event->getData();
$form = $event->getForm(); //SiteType
if($user = $site->getSiteAdmin()) $siteAdminOpts['choices'] = array($user);
$form->add('siteAdmin', UserChooserType::class, $siteAdminOpts);
});
}
//etc.
tldr;
I'd like to either:
set UserChooserType's choices option to the selected user in UserChooserType::configureOptions(), or
move ->addEventListener(...) into UserChooserType::buildForm().
Any idea how it might be done?
Here is the UserChooserType:
class UserChooserType extends AbstractType
{
/**
* #var UserManager
*/
protected $um;
/**
* UserChooserType constructor.
* #param UserManager $um
*/
public function __construct(UserManager $um){
$this->um = $um; //used to find and decorate User entities. It is not a Doctrine entity manager, but it uses one.
}
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if (!$data) return;
$user = $this->um->getUserByUserName($data);
if(!$user->getId()) $this->um->saveUser($user); //create User in db, if it's not there yet.
});
$builder->resetViewTransformers(); //so new choices aren't discarded
$builder->addModelTransformer(new CallbackTransformer(
function ($user) { //internal storage format to display format
return ($user instanceof User) ? $user->getUserName() : '';
},
function ($username) { //display format to storage format
return ($username) ? $this->um->getUserByUserName($username) : null;
}
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'class' => 'ACRDUserBundle:User',
'label' => 'ldap.user.name',
'choice_label' => function($user, $key, $index){
$this->um->decorateUser($user);
$label = $user->getDetail('displayName');
return $label ? $label : $user->getUserName();
},
'choice_value' => 'userName',
'choices' => [],
'attr' => array(
'class' => 'userchooser',
'placeholder' => 'form.placeholder.userchooser'
)
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'my_userchooser';
}
/**
* #inheritDoc
*/
public function getParent() {
return EntityType::class;
}
}
This helped me out:
https://symfony.com/doc/current/reference/forms/types/entity.html#using-choices
Here is how I used it (again with select 2):
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var ProductCollection $productCollection */
$productCollection = $builder->getData();
$builder
->add('products', EntityType::class,
[
'class' => Product::class,
'required' => FALSE,
'expanded' => FALSE,
'multiple' => TRUE,
'choices' => $productCollection->getProducts(), // We only preload the selected products. The rest come from api.
'attr' => [
'data-toggle' => 'select',
'data-options' => json_encode([
'ajax' => [
'url' => '/admin/product/autocomplete',
'dataType' => 'json'
]
])
],
'choice_label' => function (Product $product) {
return $product->getName();
},
'choice_attr' => function (Product $product) {
return [
'data-avatarsrc' => $product->getImage()->getUrl(),
'data-caption' => $product->getSkus()->first()->getSku(),
];
},
]
)
;
}
And here is my JS for the init of the select2 (copy/paste):
function() {
var elements = document.querySelectorAll('[data-toggle="select"]');
function templateResult(element) {
let avatarsrc, caption;
if (element.id && element.avatarsrc) {
avatarsrc = element.avatarsrc
caption = element.caption ? ' ' + element.caption : ''
}
else if (element.element) {
avatarsrc = element.element.dataset.avatarsrc ? element.element.dataset.avatarsrc : ''
caption = element.element.dataset.caption ? ' ' + element.element.dataset.caption : ''
}
if (!avatarsrc) {
return element.text + caption
}
let wrapper = document.createElement("div");
return wrapper.innerHTML = '<span class="avatar avatar-xs mr-3 my-2"><img class="avatar-img rounded-circle" src="' + avatarsrc + '" alt="' + element.text + '"></span><span>' + element.text + '</span><span class="badge badge-soft-success ml-2">' + caption + '</span>', wrapper
}
jQuery().select2 && elements && [].forEach.call(elements, function(element) {
var select, additionalOptions, select2options;
additionalOptions = (select = element).dataset.options ? JSON.parse(select.dataset.options) : {};
select2options = {
containerCssClass: select.getAttribute("class"),
dropdownCssClass: "dropdown-menu show",
dropdownParent: select.closest(".modal") ? select.closest(".modal") : document.body,
templateResult: templateResult,
templateSelection: templateResult
}
$(select).select2({...select2options, ...additionalOptions})
})
}(),
Some remarks first:
The field name UserChooserType is a bit elaborated. UserType is shorter and just as clear, and fits the Symfony naming conventions better
I wouldn't extend UserChooserType from EntityType. Doc says EntityType is specifically dedicated to entities coming from Doctrine. It adds magic so that the field is easily configured around Doctrine: transformers, automatic fetching of choices from the DB, etc. If your users are fully defined in the LDAP, I would instead recommend extending ChoiceType and populate your choices from the LDAP directly.
Coming back to your problem, what you want here is to leverage the almighty power of dependency injection, by declaring your field type as a service. After having done that, instead of:
$builder->add('siteAdmin', UserChooserType::class, $siteAdminOpts)
You will do (assuming you chose the form name user_chooser_type):
$builder->add('siteAdmin', 'user_chooser_type', $siteAdminOpts)
You need to inject the security.token_storage service into your field type. This is the service that holds the current user's information, you can access it like this:
$user = $securityTokenStorage->getToken()->getUser();
With this the population of the default value can happen at the field type level instead of its parent.
Using this, you could also add the LDAP choices at field type level too, by injecting a service able to communicate with the LDAP.
It should finally noted that all default symfony field types are also declared as services.
EDIT:
Previous answer is beside the point.
In Symfony, I'm not aware of any possible simple way to modify the choice list of a choice/entity field after it has been created. I don't think there is, and actually I might have done the same as you to tackle the issue you're describing.
My opinion is that you're already doing what needs to be done in this case.
If you're really motivated though, I think it might just be possible to move the event listener in the form type like this:
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($options) {
if ($form->hasParent()) {
$data = $event->getData();
$form = $event->getForm();
$method = 'get' . ucfirst($form['action_name']);
if($user = $data->$method()) $siteAdminOpts['choices'] = array($user);
$form->getParent()->add($options['form_name'], UserChooserType::class, $siteAdminOpts);
}
});
Something like that. Not sure how that would work though.
I'm still interested in seeing if anyone comes up with a clean solution.
The error message:
A new entity was found through the relationship
'AppBundle\Entity\Tarifa#pesos' that was not configured to cascade
persist operations for entity:
AppBundle\Entity\TarifaPeso#0000000072d3bd4300000000232470d3. To solve
this issue: Either explicitly call EntityManager#persist() on this
unknown entity or configure cascade persist this association in the
mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot
find out which entity causes the problem implement
'AppBundle\Entity\TarifaPeso#__toString()' to get a clue.
Tarifa.php
/**
* #ORM\OneToMany(targetEntity="TarifaPeso", mappedBy="tarifa")
*/
private $pesos;
TarifaPeso.php
/**
* #ORM\ManyToOne(targetEntity="Tarifa", inversedBy="pesos", cascade={"persist"})
* #ORM\JoinColumn(name="tarifa_id", referencedColumnName="id")
*/
private $tarifa;
TarifaType.php
->add('pesos', CollectionType::class, array(
'entry_type' => TarifaPesoType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
))
The controller...
public function newAction(Request $request)
{
$tarifa = new Tarifa();
$form = $this->createForm('AppBundle\Form\TarifaType', $tarifa);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($tarifa);
$entityManager->flush();
$this->addFlash('success', 'project.created_successfully');
return $this->redirectToRoute('admin_post_index');
}
return $this->render('admin/tarifas/new.html.twig', array(
'form' => $form->createView(),
));
}
What am I missing? Really exhausted... please any help?
You should move cascade={"persist"} annotation from TarifaPeso::tarifa to Tarifa::pesos property. Or you can explicitly persist all pesos you get from the form:
$entityManager->persist($tarifa);
foreach ($tarifa->getPesos() as $peso) {
$entityManager->persist($peso);
}
$entityManager->flush();
Ok, it is solved, now it stores the Tarifa id in the TarifaPeso table. The error was I'd removed the line from the AddPeso method:
public function addPeso(\AppBundle\Entity\TarifaPeso $pesos)
{
$this->pesos[] = $pesos;
$pesos->setTarifa($this);
return $this;
}
i am a little baffled by this;
my post forms is not populating the values received from the returned post values; i suspect the problem is arising from my getJobId() in my jobsort class values;
below is my form:
public function jobSortAction()
{
$form = new CreateJobSortForm($this->getEntityManager());
$jobSort = new JobSort();
$form->setInputFilter($jobSort->getInputFilter());
$id= 11;
$jobSort->setId($id);
$form->bind($jobSort);
if ($this->request->isPost()) {
//$post = $this->request->getPost();
$form->setData($this->request->getPost());
//var_dump($post);
//var_dump($jobSort);
if ($form->isValid()) {
$this->getEntityManager()->persist($jobSort);
$this->getEntityManager()->flush();
}
}
return array('form' => $form);
}
below is the var_dumped values of the 'return post values' and the Jobsort() object. You will note that the returned post values has values for both the Id and the JobId
object(Zend\Stdlib\Parameters)[168]
public 'JobSort' =>
array (size=2)
'jobId' => string '5' (length=1)
'id' => string '11' (length=2)
public 'submit' => string 'Submit' (length=6)
object(Workers\Entity\JobSort)[394]
protected 'inputFilter' => null
protected 'id' => int 11
protected 'jobId' => null
protected 'workerservicelist' => null
yet, when i populate the values, it does not seem to record the values for the jobId
below is my jobsort entity class:
class JobSort
{
protected $inputFilter;
/**
* #ORM\Id
*
* #ORM\Column(name="user_id", type="integer")
*/
protected $id;
/**
* #ORM\Column(name="jobId", type="integer")
*/
protected $jobId;
public function setId($id)
{
return $this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setJobId($jobId)
{
return $this->jobId = $jobId;
}
public function getJobId( )
{
return $this->jobId;
}
is there any advice or suggestions on what i need to do to find out why the values are not been populated
warm regards
Andreea
by the way; the form actually works when i had the Id of CLASS jobsort set to
#ORM\GeneratedValue(strategy="AUTO")
the problem started when i took it out and set it to manual
Hello again
here is my form:
this is the error message i received;
An exception occurred while executing 'INSERT INTO worker_main_jobsort (user_id, jobId) VALUES (?, ?)' with params [11, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'jobId' cannot be null
here is my form:
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;
use Workers\Form\Fieldset\JobSortFieldset;
class CreateJobSortForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('create-Job-post-form');
// The form will hydrate an object of type "BlogPost"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'));
// Add the user fieldset, and set it as the base fieldset
$JobSortFieldset = new JobSortFieldset($objectManager);
$JobSortFieldset->setUseAsBaseFieldset(true);
$this->add($JobSortFieldset);
// Optionally set your validation group here
// … add CSRF and submit elements …
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Submit',
'id' => 'submitbutton',
),
));
// Optionally set your validation group here
}
}
and here is the fieldset class:
class JobSortFieldset extends Fieldset
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('JobSort');
$id= 10;
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'))
->setObject(new JobSort());
}
}
this addition is in response to rafaame solution;
i amended my form as recommended; however it still not working. i think the issue now is that Rafaame solution is in regarding to zendDB save method, but i am using doctrine persis**t and **flush method . i accordingly get the following error message;
Call to undefined method Workers\Entity\JobSort::save()
below is my amended form:
public function jobSortAction()
{
$form = new CreateJobSortForm($this->getEntityManager() );
$jobSort = new JobSort();
if($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid())
{
$entity = $form->getData();
$model = new JobSort();
$model->save($entity);
// $this->getEntityManager()->persist( $model);
// $this->getEntityManager()->flush();
}
}
return array('form' => $form);
}
in response to Rafaame question about what problems i had,the message that i am now receiving is this:
**
EntityManager#persist() expects parameter 1 to be an entity object,
array given.
**
below is my function:
public function jobSortAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getEntityManager();
$form = new CreateJobSortForm($this->getEntityManager());
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid()) {
$entity = $form->getData();
$model = new JobSort($objectManager, $serviceLocator);
$model->getEntityManager()->persist($entity);
$model->getEntityManager()->flush();
}
}
return array('form' => $form);
}
my form; i.e where the hydrator should be set
namespace Workers\Form;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;
use Workers\Form\Fieldset\JobSortFieldset;
class CreateJobSortForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('JobSort');
// The form will hydrate an object of type "BlogPost"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'));
// Add the user fieldset, and set it as the base fieldset
$JobSortFieldset = new JobSortFieldset($objectManager);
$JobSortFieldset->setUseAsBaseFieldset(true);
$this->add($JobSortFieldset);
If you check your code, you are creating a JobSort entity, setting only its id and binding it to the form:
$jobSort = new JobSort();
$jobSort->setId($id);
$form->bind($jobSort);
After that, you are dumping $jobSort and $this->request->getPost(). So, obviously, you are getting jobId in the POST data but not in the entity (you didn't set the entity's jobId before binding it to the form). There's nothing wrong with your entity's code.
The solution for this: don't bind anything to the form. You should only bind an entity to the form in the case of an edit action, that you fetch the entity from the database and want to populate the form with its values.
Example of add action:
public function addAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getObjectManager();
$form = new Form\EmailCampaign\Add($serviceLocator, $objectManager);
if($this->request instanceof HttpRequest && $this->request->isPost())
{
$form->setData($this->request->getPost());
if($form->isValid())
{
$entity = $form->getData();
//If you want to modify a property of the entity (but remember that it's not recommended to do it here, do it in the model instead).
//$entity->setJobId(11);
$model = new Model\EmailCampaign($serviceLocator, $objectManager);
$model->save($entity);
if($entity->getId())
{
$this->flashMessenger()->addSuccessMessage('Email campaign successfully added to the database.');
return $this->redirect()->toRoute('admin/wildcard', ['controller' => 'email-campaign', 'action' => 'edit', 'id' => $entity->getId()]);
}
else
{
$this->flashMessenger()->addErrorMessage('There was an error adding the email campaign to the database. Contact the administrator.');
}
}
}
return new ViewModel
([
'form' => $form,
]);
}
Example of edit action:
public function editAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getObjectManager();
$form = new Form\EmailCampaign\Edit($serviceLocator, $objectManager);
$id = $this->getEvent()->getRouteMatch()->getParam('id');
$entity = $objectManager
->getRepository('Application\Entity\EmailCampaign')
->findOneBy(['id' => $id]);
if($entity)
{
$form->bind($entity);
if($this->request instanceof HttpRequest && $this->request->isPost())
{
$form->setData($this->request->getPost());
if($form->isValid())
{
//If you want to modify a property of the entity (but remember that it's not recommended to do it here, do it in the model instead).
//$entity->setJobId(11);
$model = new Model\EmailCampaign($serviceLocator, $objectManager);
$model->save($entity);
$this->flashMessenger()->addSuccessMessage('Email campaign successfully saved to the database.');
}
}
}
else
{
$this->flashMessenger()->addErrorMessage('A email campaign with this ID was not found in the database.');
return $this->redirect()->toRoute('admin', ['controller' => 'email-campaign']);
}
return new ViewModel
([
'form' => $form,
'entity' => $entity,
]);
}
Hope this helps.
EDIT:
What I provided was an example of how to handle the form and the entities with Doctrine 2 + ZF2.
What you have to keep in mind is that Doctrine doesn't work with the concept of models, it just understands entities. The model I'm using in my application is a concept of the MVC (Model-View-Controller) design pattern (that ZF2 uses) and I have decided to wrap the entity manager calls (persist and flush) inside my model's method, that I named save() (in the case the entity needs some special treatment before being save to the database and also because it is not a good practice to use the entity manager directly in the controller - see this slide of Marcos Pivetta presentation http://ocramius.github.io/presentations/doctrine2-zf2-introduction/#/66).
Another thing that you may be misunderstanding is that when you do $form->getData() to a form that has the DoctrineObject hydrator, it will return you the entity object, and not an array with the data (this last happens if it has no hydrator). So you don't need to create the entity after doing $form->getData(), and if you do so, this created entity won't have any information provided by the form.
Your code should work now:
public function jobSortAction()
{
$serviceLocator = $this->getServiceLocator();
$entityManager = $this->getEntityManager();
$form = new CreateJobSortForm($entityManager);
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid()) {
//I'm considering you are setting the DoctrineObject hydrator to your form,
//so here we will get the entity object already filled with the form data that came through POST.
$entity = $form->getData();
//Again, if you need special treatment to any data of your entity,
//you should do it here (well, I do it inside my model's save() method).
//$entity->setJobId(11);
$entityManager->persist($entity);
$entityManager->flush();
}
}
return array('form' => $form);
}
Good morning everyone! Is a form of.
Class ReleasesType:
$builder
->add('doid', 'text')
->add('dourl', 'text')
->add('artists', 'entity', array(
'class' => 'MReleaseCoreBundle:Artists',
'property' => 'name',
'expanded' => true ,
'multiple' => true
));
Сonnection with them one-to-many:
Class 'Artists':
/**
* #ORM\OneToMany(targetEntity="ReleasesArtists" , mappedBy="artists" , cascade={"all"})
* */
private $da;
public function __construct() {
$this->da = new \Doctrine\Common\Collections\ArrayCollection();
}
Class 'ReleasesArtists':
/**
* #ORM\ManyToOne(targetEntity="Releases", inversedBy="da")
* #ORM\JoinColumn(name="releases_id", referencedColumnName="id")
* */
private $releases;
/**
* #ORM\ManyToOne(targetEntity="Artists", inversedBy="da")
* #ORM\JoinColumn(name="artists_id", referencedColumnName="id")
* */
private $artists;
And of course the entity 'Releases':
/**
* #ORM\OneToMany(targetEntity="ReleasesArtists" , mappedBy="releases", cascade={"all"} , orphanRemoval=true)
*/
private $da;
public function getArtists() {
$artists = new ArrayCollection();
foreach($this->da as $p) {
$artists[] = $p->getArtists()->getName();
}
return $artists;
}
public function addDa($da) {
$this->da[] = $da;
}
public function setArtists($artists) {
foreach($artists as $p) {
$po = new \MRelease\CoreBundle\Entity\ReleasesArtists();
$po->setReleases($this);
$po->setArtists($p);
$this->addDa($po);
}
}
Connection is working correctly, all outputs. But does not "checked". In what may be the problem?
Thanks!
Into your controller, where you build and output your form, you have to do something like this
public function myFooAction(Request $request, $releasesId)
{
$repo = $this->getDoctrine()->getManager()->getRepository('YourBundleName:Releases');
$releasesObject = $repo->findOneById($releasesId);
$form = $this->createForm(new ReleasesType(), $releasesObject);
return $this->render('YourBundle::TemplateToRender, array('form'=>$form);
}
What happen here, and why is working?
I've made some assumptions as you don't provide any controller code. First of all, I assume that you have an action like myFooAction() where you do form operation and I suppose, also, that you pass to this action an id for load object from DB and tie it to your form - if I understood correctly your question.
So, first line of action is for retrieve repository for this object. Once you've got repo, you can fetch your object (second line). On third line I use Symfony2 form's facility and "connect" object to his form type: with this, all values contained into this object will be reported into your form (so checkboxes will have correct value). Last line is for render form.
Obviously, your action logic could be different but concept expressed here could be replicated with "different" implementation everywhere.
I'm using Doctrine 2 in a Zend Framework application and require functionality similar to Zend_Validate_Db_RecordExists and Zend_Validate_Db_NoRecordExists.
For example, when a user enters a new item, I need to validate that a duplicate entry doesn't already exist. This is easy to accomplish with Zend_Db by adding the Db_NoRecordExists validator on my forms.
I tried implementing the custom-validator solution proposed here, but I can't figure out how they are communicating with Doctrine to retrieve entities (I suspect this approach may no longer work post-Doctrine 1.x).
The FAQ section of the Doctrine manual suggests calling contains() from the client code, but this only covers collections, and if possible I'd like to handle all of my form validation consistently from within my form models.
Can anyone suggest a way to use these Zend validators with Doctrine 2 DBAL configured as the database connection/resource?
It's quite straightforward, really.
I have a few Zend_Validate-type validators that talk to Doctrine ORM, so I have an abstract class that they descend from.
Here's the abstract class:
<?php
namespace TimDev\Validate\Doctrine;
abstract class AbstractValidator extends \Zend_Validate_Abstract{
/**
* #var Doctrine\ORM\EntityManager
*/
private $_em;
public function __construct(\Doctrine\ORM\EntityManager $em){
$this->_em = $em;
}
public function em(){
return $this->_em;
}
}
Here's my NoEntityExists validator:
<?php
namespace TimDev\Validate\Doctrine;
class NoEntityExists extends AbstractValidator{
private $_ec = null;
private $_property = null;
private $_exclude = null;
const ERROR_ENTITY_EXISTS = 1;
protected $_messageTemplates = array(
self::ERROR_ENTITY_EXISTS => 'Another record already contains %value%'
);
public function __construct($opts){
$this->_ec = $opts['class'];
$this->_property = $opts['property'];
$this->_exclude = $opts['exclude'];
parent::__construct($opts['entityManager']);
}
public function getQuery(){
$qb = $this->em()->createQueryBuilder();
$qb->select('o')
->from($this->_ec,'o')
->where('o.' . $this->_property .'=:value');
if ($this->_exclude !== null){
if (is_array($this->_exclude)){
foreach($this->_exclude as $k=>$ex){
$qb->andWhere('o.' . $ex['property'] .' != :value'.$k);
$qb->setParameter('value'.$k,$ex['value'] ? $ex['value'] : '');
}
}
}
$query = $qb->getQuery();
return $query;
}
public function isValid($value){
$valid = true;
$this->_setValue($value);
$query = $this->getQuery();
$query->setParameter("value", $value);
$result = $query->execute();
if (count($result)){
$valid = false;
$this->_error(self::ERROR_ENTITY_EXISTS);
}
return $valid;
}
}
Used in the context of a Zend_Form (which has an em() method like the abstract class above):
/**
* Overrides superclass method to add just-in-time validation for NoEntityExists-type validators that
* rely on knowing the id of the entity in question.
* #param type $data
* #return type
*/
public function isValid($data) {
$unameUnique = new NoEntityExists(
array('entityManager' => $this->em(),
'class' => 'PMS\Entity\User',
'property' => 'username',
'exclude' => array(
array('property' => 'id', 'value' => $this->getValue('id'))
)
)
);
$unameUnique->setMessage('Another user already has username "%value%"', NoEntityExists::ERROR_ENTITY_EXISTS);
$this->getElement('username')->addValidator($unameUnique);
return parent::isValid($data);
}
Check out the RecordExists.php and NoRecordExists.php classes in my project:-
https://github.com/andyfenna/AJF-IT/tree/master/library/AJFIT/Validate
I hope these are some use to you.
Thanks
Andrew