I'm sorry for my bad English, I'm not a native speaker. Feel free to correct my text if needed.
The question is really simple (it's in the end of this text), but I've written a rationale with some research and tests.
Rationale
If you want, you can skip this rationale and jump directly to the question itself.
I've been trying for several hours on trying to get a ManyToMany relationship to persist from the inverse side using doctrine:generate:entities and doctrine:generate:crud on Symfony2 console.
From the owning side, the relationship is saved in the database with the generated crud out of the box, but not from the inverse side (this is expected: http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#owning-side-and-inverse-side)
What I want is to make it work from the inverse side as well without changing the autogenerated controllers; I'd like to change only the model (entity).
The easy way would be to add a couple of custom code lines to the controller:
// Controller that works the way I want
// Controller/AlunoController.php
...
public function createAction(Request $request)
{
$entity = new Aluno();
$form = $this->createForm(new AlunoType(), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
// begin custom code
foreach ($entity->getResponsaveis() as $responsavel) {
$responsavel->addAluno($entity);
}
// end custom code
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('aluno_edit', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
...
The previous code works, but it is not what I want, because those custom lines of code refer to the relationship logic, and should be centralized in the Entity itself (if not, it would have to be duplicated all over the controllers that update this entity).
So, what I did next was to change my adders and removers in the Entity file to execute that required logic, adding the code to automatically update both inverse and owning side (as recommended in http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#picking-owning-and-inverse-side and https://stackoverflow.com/a/7045693/1501575)
// Entity/Aluno.php
...
/**
* Add responsaveis
*
* #param \MyBundle\Entity\Responsavel $responsaveis
* #return Aluno
*/
public function addResponsavei(\MyBundle\Entity\Responsavel $responsaveis)
{
/* begin custom code */
//var_dump('addResponsavei');
$responsavel->addAluno($this);
/* end custom code */
$this->responsaveis[] = $responsaveis;
return $this;
}
...
This should work, because it does work when the same code is in the controller, but it actually does not.
The problem is that the method Aluno##addResponsavei() is never being called when $form->bind($request) runs in the controller (first code sample up there) (I realized this with that var_dump() line. I've also put var_dumps in some other getters and those other methods were called as normal).
So, all the regular setters are indeed called inside $form->bind($request), but not this one. This is weird, because the method names were autogenerated by `doctrine:generate:entities', which I assumed would make $form->bind() know how to call all the setters, getters and adders.
Question
Why is $form->bind() not calling the adder method (Aluno##addResponsavei())?
Is there a special naming convention not followed by doctrine:generate:crud that is preventing the method from being found and executed?
Solution
Thanks for the comment from user1452962 and later the answer from Elnur Abdurrakhimov, I've got it to work and it is actually pretty simple.
All I had to do is to add the option 'by_reference' to false in the properties that hold the inverse side of the relationship, and suddenly addResponsavei() began to be called.
// Form/AlunoType.php
...
$builder
->add('nome')
->add('cidade_natal')
->add('nascimento')
->add('email')
->add('endereco')
->add('nome_sem_acento')
->add('data_hora_cadastro')
->add('responsaveis', null, array('by_reference' => false))
->add('turmas', null, array('by_reference' => false))
...
This way, the relationship logic is gone from the controller, and that what I was looking for. Thank you guys.
You need to set the by_reference option to false in order for adders to be called.
Related
For my Poll Application i created a FormType called CampaignType which holds a CollectionType named blocks which in turn holds a CollectionType named lines, which holds a CollectionType named fields, which holds a CollectionType named pollResults.
In my next code example you can see my code that renders the View to fill a campaign(poll).
public function fillAction(Request $request, $id)
{
$campaign = $this->getDoctrine()->getRepository(Campaign::class)->find($id);
$entityManager = $this->getDoctrine()->getManager();
foreach ($campaign->getBlocks() AS $block){
foreach ($block->getLines() AS $line){
foreach ($line->getFields() AS $field){
$pollResult = new PollResult();
$pollResult->setCampaign($campaign);
$pollResult->setField($field);
$pollResult->setUser($this->getUser());
$entityManager->persist($pollResult);
$field->getPollResults()->add($pollResult);
}
}
}
$form = $this->createForm(CampaignType::class, $campaign);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
var_dump("true");
//$entityManager->persist($campaign);
$entityManager->flush();
return $this->redirectToRoute("grappt_poll_campaignShow", ['id' => $id]);
}
return $this->render('GrapptPollBundle:Campaigns:fill.html.twig', [
'campaign' => $campaign,
'form' => $form->createView()
]);
}
The only thing that must be persisted in the database are the PollResults.
Every PollResult has an entry for the campaign_id and the field_id it belongs to, the user_id who filled out the campaign and the value the user chose (and of course its own id, which gets generated automatically).
My Problem is that i don't know how to do that.
Where do i have to call $entityManager->persist($pollResult);.
Right now i put it directly under the initialization-stuff.
Do i have to put it into the if($form->isSubmitted() && $form->isValid())-query and loop through every pollResult?
Do i have to call $entityManager->persist($campaign); although nothing changes there?
Furthermore i wonder if i have to add something for the value-entry of each PollResult?
Thanks in advance for every answer
lxg
What will $form->isValid() return ?
It will depend on the validation constraints of you master form. If your validation constraints are in the annotations of your entity, in your master entity you should have the #Assert\Valid() annotation which will be sure that the nested form is valid :
class Campaign
{
/**
* #ORM\OneToMany(…)
* #Assert\Valid() // <- this line here
*/
private $blocks;
...
If you prefer to put your validation constraints in your CampaignType, you can put it in the options :
public function buildForm (FormBuilderInterface $builder, array $options)
{
$builder
->add('blocks', CollectionType::class,[
'entry_type' => BlockType::class,
'constraints' => array(new Valid()) // <- this line here
...
So, where should you put the persist()?
The best is to have Symfony's form validation (->isValid()) before any persistance, for security and data sanity (don't persist before ensuring csrf protection for instance). If you may add a lot of data (like persisting thousands of entities after one form submission), you can look into Doctrine's batch processing and bulk inserts : https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/batch-processing.html
Should you also persist the Campaign object ?
It depends on the cascade persistence rules you have in your entity.
You can find all the rules to fine-tune the cascade here : https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/working-with-associations.html#transitive-persistence-cascade-operations
I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.
Route Method¶ There is a shortcut #Method annotation to specify the
HTTP method allowed for the route. To use it, import the Method
annotation namespace:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* #Route("/blog")
*/
class PostController extends Controller
{
/**
* #Route("/edit/{id}")
* #Method({"GET", "POST"})
*/
public function editAction($id)
{
}
}
I've seen a lot of developers limiting the Method to either only GET or POST,
but since the controller allows both by default, why do developers choose to restrict it to only one method? is this some kind of security measure? and if yes what kind of attacks would that protect you from?
First, there are several method available following the spec, not only GET and POST
I don't think this is a security reason, it's more a matter of respecting standards (e.g REST methods).
I personally use different methods for several behaviours. For me, there's the action of SEEING the edition, and APPLYING the edition.
That's two different behaviours for a single URL. Even if the response at the end will tends not to change, the behaviour at controller level is different.
I think this is a matter of personnal preference, I like rather see
/**
* #Route("/edit")
* #Method({"GET"})
* #Template
*/
public function editAction()
{
$obj = new Foo;
$obj->setBaz($this->container->getParameter('default_baz'));
$type = new FooType;
$form = $this->createForm($type, $obj, array(
'action' => $this->generateUrl('acme_foo_bar_doedit'),
'method' => 'PUT'
));
return array(
'form' => $form->createView()
);
}
It's pretty clear what it does. It just instanciates the form you need, no user input are processed.
Now, you can add your action to process the edition by adding a second method
/**
* #Route("/edit")
* #Method({"PUT"})
* #Template("AcmeFooBundle:Bar:edit.html.twig")
*/
public function doEditAction(Request $request)
{
$obj = new Foo;
$type = new FooType;
$form = $this->createForm($type, $obj, array(
'action' => $this->generateUrl('acme_foo_bar_doedit'),
'method' => 'PUT'
));
$form->handleRequest($request);
if ($form->isValid()) {
// Play with $obj
}
return array(
'form' => $form->createView()
);
}
Easy too, and can easily be used elsewhere in your application (rather than in the default edition page)
I personally ALWAYS define a request method (POST, GET, PUT etc etc). I think that (especially with RESTful API's) this is transparent. It can protect you against some attacks, because you are limiting the methods that can be used. It also makes sense, because if you login you POST data and don't GET it and if you request an article, you DO want to GET it :) See what I mean? Only the 'it makes it more transparent' has catched me already. I'm hooked to always defining the methods, whether it's only for clarity or anything else.
edit: Haven't seen the other answer yet (must've been added while I pressed the submit button :) )
There are a lots of reasons to choose between POST, GET, PUT and DELETE methods (or Http verbs). first of all there are some limitations in using GET method for example you can not include large amount of data in URL query string or MULTI-PART forms for uploading files.
And there are many security considerations about using POST and GET methods and I don't even know where to start from. You can Google that. And finally in RESTful web services convention CRUD (Create/Retrieve/Update/Delete) operations are mapped to Http methods (POST/GET/PUT/DELETE). For example:
path: /student/{id}, method GET returns a student
path: /student, method POST creates a student
path: /student, method PUT updates student info
One of the most important security reasons would be that URLs are usually logged in ISPs, Apache web server and network devices(firewalls, ...) and if you include sensitive data such as session ids, ... your data will be stored in plain text in so many places that you have no idea about. Also i recommend have a look at OWASP top 10.
I want to return a value from entity to view file. Below is my entity function
public function getVisitorName($id)
{
$repository = $this->getDoctrine()->getRepository('SystemVmsBundle:VisitorsDetails');
$product = $repository->findOneBy(array('id' =>$id));
$name=$product->getFirstname();
return $name;
}
This is the line in my view file which calls that function
{{ entity.visitorName(entity.visitorId) }}
Its not giving me any error. But only a blank page. How can i fix this?
This is my controller code
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('SystemVmsBundle:EntryDetails')->findAll();
return array(
'entities' => $entities,
);
}
I am trying to fetch the visitors name(from visitors table) corresponding to the visitor id(in entry table).How will i do it then?
you have two ways of doing it:
1) Map your SystemVmsBundle:EntryDetails entity, to SystemVmsBundle:VisitorsDetails as OntToOne by adding field details to your EntryDetails; , and then in twig template just call it via
{{ entity.details.name }}
2) instead of creating getVisitorName(), it is better to create twig function for this, with same functionality.
Your indexAction() is not returning a response object, it is just returning an array of entities. Controller actions should return a Response containing the html to be displayed (unless they are for e.g. ajax calls from javascript). If you are using twig templates you can use the controller render() method to create your response, something like this:
return $this->render('<YourBundle>:<YourViewsFolder>:<YourView>.html.twig', array(
'entities' => $entities,
));
When you've corrected that I suspect you'll get an error because $this->getDoctrine() won't work from an entity class. The code you have in the getVisitorName() method just shouldn't be in an entity class.
As #pomaxa has already suggested, I believe there should be a relationship between your EntryDetails and VisitorsDetails entities although I don't know enough about your data from the question to know what type of relationship it should be (OneToOne / ManyToOne). If your EntryDetails entity had a relationship to VisitorsDetails, the EntryDetails class would then contain a $visitorsDetails attribute and methods to get/set it. Then the line in your twig file would look like this:
{{ entity.visitorsDetails.firstName }}
There is a section on Entity Relationships / Associations in the Symfony Manual.
Also, I hope you don't mind me giving you a little advice:
Be careful when you copy and paste code as it appears you have done in getVisitorName(). You have kept the variable name '$product' although there are no products in your system. This sort of thing can cause bugs and make the code more difficult to maintain.
I recommend you avoid tacking 'Details' onto the end of entity names unless you genuinely have two separate and related entities such as Visitor + VisitorDetails and a good reason for doing so. I think the entities in your example are actually 'Visitor' and 'VistorEntry'.
Unless you are writing a re-usable component, I recommend you use specific variable names like '$visitorEntries' rather than '$entities' in your controller and twig.
In general, the more meaningful your variable names, the more readable, maintainable and bug-free your code is likely to be. Subsequently, it will also be much easier for people on SO to understand your code and give you help when you need it.
I have a template where I render a widget which contains a form:
{{ render(controller('ApplicationDemoBundle:Demo:newWidget', {'demo' : entity })) }}
The newWidgetAction calls a createAction:
public function createAction(Request $request)
{
$entity = new Demo();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('demo_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
)
// Something like this would be awesome with passing the form containing errors with
// return $this->redirect($this->getRequest()->headers->get('referer'));
}
Imagine the submitted form (user acts in show theme) produces an error. This would make a return to the newWidget template which does not display the full layout.
My question is now: What is the right way to pass the errors from the child controller (newWidget) to the main template (show)?, without modifying the showActions function parameter to pass the formerrors over there.
There is a similar thread to this question: Symfony2 render and form
In this case where sessions used but I'm more than curious if this is the way to go.
The problem is that each fragment (a sub-controller) uses a virtual request. This is to protect the original request from modification by possibly unexpected forwards, and a fragment is essentially a forward taking place during a rendering stage.
It is possible to access the top level request using:
$this->container->get('request'); then handing the request with the form in the fragment, but this may get very confusing very quickly if you are using multiple forms per page.
My strategy is to follow a convention that limits the number of validated form on a page to just one. Any other forms don't require validation, or it is otherwise impossible for a form to be submitted incorrectly (hacked forms would throw server side exceptions, but the user should only see those if they are being naughty).
Try to structure your template inheritance to accommodate navigation to forms, while always showing most of the same layouts and data. You can do this by expanding the use of fragments which gives the bonus of separating your display logic.