whats the point of using the #Method annotation - symfony

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.

Related

What is the best way to create a singleton entity in Symfony 4?

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.

parse a URL with a known route to get parameters (reverse/backwards of normal usage)

I hope I may ask this question without going into the details of why I need to do this,
I have a route
/**
* #Route("/edit/{id}", name="my_edit_route")
*/
public function editAction()
{ /* ....... */ }
And now at some other point in time, I have a URL which was generated with this route (I only have the string, no other information is available). The string looks like this
/edit/23
Is there anyway in Symfony2, that I can provide the route name (e.g. "my_edit_route" and this URL string, and have Symfony extract the parameters. (e.g. the id=23)
p.s. I know this could be done with regex or other tools, but my actual routes in my application are more complex.
I am sure this should not be to difficult for Symfony2 to do, as it already does this each time it extracts the parameters from your URL to handle a request.
you no need to route name
$params = $this->get('router')->match('/edit/23');
that return $params as:
array(
'id' => '23',
'_controller' => 'AppBundle:Blog:show',
)

Doctrine event subscribers - performance and convention

I've got a basic question regarding the right way to do things in Symfony2, with specific emphasis on Doctrine event subscribers. I know how to implement them, but something has been bugging me. Currently, I have the following class.
namespace MyProject\MainBundle\EventSubscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use MyBundle\MainBundle\Entity\LandingPageAggregator;
class LandingPageAggregatorSubscriber implements EventSubscriber {
/**
* Returns an array of events this subscriber wants to listen to.
*
* #return array
*/
public function getSubscribedEvents() {
return array(
'prePersist',
'preUpdate',
);
}
public function prePersist(LifecycleEventArgs $args) {
$entity = $args->getObject();
if (!$entity instanceof LandingPageAggregator)
return;
// Adittional stuff here...
}
}
I got this from this Symfony article, and it's working fine, my question is just the following:
Is there a better way to do this? Is this actually the accepted standard way of setting stuff like "Posted By" or "Created Date" fields?
Is this not performance intensive? Forgive me if I'm wrong, but registering 100 of these, doesn't that mean that for every single persist to the database, Symfony has to run thorugh all 100 subscribers and call the prePersist method on each of them? This seems like a giant waste of resources, thus the purpose of this question.
If I'm correct with number 2. above here, is there any better/less intensive method of doing the same thing? I just read on doctrine's documentation that they've introduced a new annotation as of 2.4, but I'm not using that version yet. Will that solve this issue in any case?
As a side question, what is the difference between the listener and subscriber as stated in the Symfony documentation linked above?
Thanks for any advice here!

Symfony2, $form->bind() not calling adder methods of entity

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.

Symfony2 Application Architecture - how to make a function available in all controllers?

I'm building an application where users are tied to accounts (as in, multiple users all share an account), then other entities - lets call them products are tied to the accounts. The products are associated with the accounts and only users that are tied to that account can edit the products.
The difference being in my case, there are many different entities being shared in the same model.
If it was just the one (product) entity, it wouldn't be a problem to have a method in my ProductRepository like:
public function checkOwnership($id, $account)
{
$count = $this->createQueryBuilder('s')
->select('count(s.id)')
->where('s.account = :acc')
->andWhere('s.id = :id')
->setParameters(array('id' => $id, 'acc' => $account))
->getQuery()
->getSingleScalarResult();
if($count == 0)
throw $this->createNotFoundException('ye..');
return;
}
To make sure the id of the product is tied to the user's account.
The account argument is passed to this function by using:
$this->getUser();
In the controller.
What is the best way for me to make this function available to the other entities to prevent code duplication? There are (currently) only 3 other entities, so it wouldn't be the end of the world to just have it in each repository, but I'm just wondering if there were a way to create a 'common' or global repository that follows good standards? I'd sure like to know about it.
Edit:
Or have I just completely over-thought this? Can I just create a 'Common' directory in my 'MainBundle', define a namespace and add a use statement at the start of my controllers that need access to the function?
I hope I fully understand your question.
Solution one, duplication, easy: let #ParamConverter do the job (automatic response to 404 if doesn't exist)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/pets/{id}")
*/
public function showAction(Pet $pet)
{
// Current logged user
$currentUser = $this->get('security.context')->getToken()->getUser();
// Owner
$ownerUser = $pet->getUser();
if(!$currentUser->equals($ownerUser)) {
// 401, 403 o 404 depends on you
}
}
Solution two, DRY, a bit harder: use JMSAOPBundle and define an interceptor that intercepts all request to you controller and actions (show, delete, etc.). The pointcut (connected to the interceptor) will get the current user from the context and the owner from the entity. Then you do the check...

Resources