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!
Related
I wanted to try Symfony-ux LIveComponent for a project, a small classic collectionType form with a delete button for each card, an add button to add cards as you go, fill in the generate fields and save everything with a save button.
I used a lot of help from symfony documentation, everything works except data saving, it's a bit annoying, the example I'm going to put below uses symfony documentation but it's not taken into account, maybe I forgot something, but in the current state of things I'm a bit lost.
If someone who has already used the LIve Component of symfony and had this problem would have some time, or just someone available for help it would be great :)
Note that I have no error, the ajax that is triggered is correct.
Here is the creation of my form and its save function:
public Campaign $campaign;
protected function instantiateForm(): FormInterface
{
return $this->createForm(ListMissionFormType::class, $this->campaign);
}
#[LiveAction]
public function save(Request $request, EntityManagerInterface $entityManager): Response
{
$this->submitForm();
$this->validate();
$mission = $this->getFormInstance()->getData();
$entityManager->persist($mission);
$entityManager->flush();
$this->addFlash('success', 'Modification enregistré');
return $this->redirect($request->headers->get('referer'), Response::HTTP_SEE_OTHER);
}
I want to use the getDoctrine and getManager functions in an entity. Is this possible? or is there any way arround this? I want to insert something in a database like this :
$history = new Policy();
$history->setName($file1->getClientOriginalName());
$history->setPolicyNumber($this->getPolicyNumber());
$history->setOrderId($this->getOrderId());
$history->setPath($this->getPathFile1());
$history->setDocumentType($this->getDocument1Type());
$history->setPrintAction($this);
$em = $this->getDoctrine()->getManager();
$em->persist($history);
$em->flush();
With Doctrine ORM, Entities have an unique role : data containers!
According to Doctrine architecture, there is no reason to inject EntityManager inside.
If you need to do that, you're trying to put some code of the Business layer into layer.
So try to move your code into a service, like a manager for your Entity or if you're lazy in a controller but it's a bit crapy.
I would venture to first answer the question, and then give out advice.
If you look into source code of Doctrine2, you may to find this method in Doctrine\ORM\UnitOfWork:
/**
* #param ClassMetadata $class
*
* #return \Doctrine\Common\Persistence\ObjectManagerAware|object
*/
private function newInstance($class)
{
$entity = $class->newInstance();
if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
$entity->injectObjectManager($this->em, $class);
}
return $entity;
}
So... it means, if your entity implements \Doctrine\Common\Persistence\ObjectManagerAware you will have EntityManager inside Doctrine2 entity. That's it.
Now advice:
IT'S REALLY BAD PRACTICE, AND NOT RECOMMENDED FOR USE.
From PhpDoc of \Doctrine\Common\Persistence\ObjectManagerAware interface:
Word of Warning: This is a very powerful hook to change how you can work with your domain models.
Using this hook will break the Single Responsibility Principle inside your Domain Objects
and increase the coupling of database and objects.
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'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...
Okay, so I'm not even sure how to ask this question (much less search for it). But in my system, I have a variable that forms a relationship for nearly every row. The user does not know it and it is set as a session variable each time a user logs in.
I need this variable to be available to Doctrine. It's not a default or static, so setting it in the class property isn't an option. Having it as a hidden form poses a security risk. I'm honestly at a loss. I've avoided the problem until I can't avoid it no more...
It'd accept a workaround for the time being. I really need to get this project launched as soon as humanly possible.
Any help would be greatly appreciated. Even help explaining what I'm trying to accomplish would be appreciated!
While this doesn't resolve my issue entirely, this particular solution may help someone else in a similar predicament...
In order to inject (I use the term loosely) an object into my form data using a form extension and an event listener.
Extension:
<?php
namespace Acme\DemoBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvents;
use Acme\DemoBundle\Form\EventListener\MyListener;
class FormTypeMyExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return 'form'; // because we're extending the base form, not a specific one
}
public function buildForm(FormBuilder $builder, array $options)
{
$listener = new MyListener($this->security, $this->em);
$builder->addEventListener(FormEvents::SET_DATA, array($listener, 'onSetData'));
}
}
Listener:
<?php
namespace Acme\DemoBundle\Form\EventListener;
use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityManager;
class MyListener
{
public function onSetData(FilterDataEvent $event)
{
// I use form.set_data because it has a method to set data to the form.
$form = $event->getForm();
$data = $event->getData();
// do things to the form or the data.
}
}
(Had some help from this site.)
That allows you to do anything to the form or form data to every form. Which is how I injected the object initially. My problem is the embedded forms apparently don't call setData() (presumably because the first object already has the other objects).
I've worked on this all day, so, if my answer is badly worded, complain and I'll fix it in the morning!