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

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...

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.

How to make multiple table connections in symfony2 Repository ? Entity?

I have tables like
profile status
Profile.class
id name
1 taro
2 jiro
3 john
Status.class
id profile school date
1 1 highschool 2017-04-01
2 1 juniorhighschool 2013-04-01
3 2 highschool 2017-04-01
Status is added when status changes.
So I normally choose latest status every time I need status.
$ss = $this->em->createQuery(
"SELECT cm FROM UserBundle:Status s where c.profile = :p order by desc")
->setParameters(['p' => $profile])->getResult();
$ss[0] // Latest Status
So now I would like to put this in function.
What I want to do is getting latest status from profile.
I have a few ideas
Put this function in Profile Entity?
put this function in Profile Repository?
put this function in service???
In my opinion it should be the feature of Profile Entity, So I would like to put this in Entity though, access another from an Entity is bad manner.
Is it OK to access another entity from a Profile Repository??
Or should I use service??
You can achieve this with a method in ProfileRepository
<?php
public function getLastStatusByProfile(Profile $profile)
{
// do our query from Profile with a join on Status
}
And please, use a LIMIT 1 on your query, you only need the last result
You cannot put this in an Entity because Entities cannot be injected the Doctrine EntityManager dependency ($this->em).
To perform your "getLatestStatus()" function you need the EntityManager $this->em.
To access the EntityManager you can:
get it from the container in a Command or a Controller (e.g. in a Controller $this->get('doctrine')->getManager();)
inject it into a Service using Dependency Injection configuration files (see http://symfony.com/doc/current/service_container.html#injecting-services-config-into-a-service)
use it in a Repository because Repositories have native access to it
Usually people put functions such as getLatestStatus() in a repository, the repository becomes "the class where we put all DQL queries" and this works quite fine. This is recommanded by the official Documentation (https://symfony.com/doc/current/doctrine/repository.html) "Methods containing your query logic can then be stored in this class."
It is usual in Symfony Applications to have:
Entities having only properties, getters, setters, and some additionnal logical functions (like activate(), disable() ... functions that modify the Entity properties)
Repositories to hold DQL queries with complex logic such as getLatestStatus()
Services to hold other any other functions that read / modify data
Controllers are only gateways to use Services
So one complete example would be:
<?php
class ProfileRepository extends EntityRepository
{
/**
* #param Profile $profile
*
* #return Status
*/
public function getLatestStatus($profile)
{
$qb = $this->getEntityManager()->createQuery(
"SELECT cm FROM UserBundle:Status s where c.profile = :p order by desc")
->setParameters(['p' => $profile])
->getResult();
return $result;
}
}
And do not forget to handle the case where there is no "status" available for this profile. Do you wish to return null, raise an Exception or return a default status ?

Doctrine 2, prevent getting unjoined entities

given a user and his coupons, I want to get a user and all of his coupons:
foreach ($this->createQueryBuilder('x')->select('u, c')->where('x.email = ?0')->setParameter(0, $email)->leftJoin('u.coupons', 'c')->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
this is very good until I forget to join the coupons:
foreach ($this->createQueryBuilder('x')->select('u')->where('x.email = ?0')->setParameter(0, $email)->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
sadly this still works even though no coupons were joined. Here it does an other SELECT. In additional, this 2nd select will be wrong. Id rather want to get a exception or AT LEAST an empty array instead. Is there any workaround for this?
What you're experiencing is expected doctrine behavior.
When you select a User entity, Doctrine will get the record from the database. If you aren't explicitly joining the Coupon entity (or any other entities with relationship to User), Doctrine will create a Proxy object. Once you access this proxy object by calling $user->getCoupons(), Doctrine will fire a new query to the database to get the coupons for your User entity. This is called lazy-loading.
I'm not sure if there is a way to change this in the way you described.
What you can do is to create a method in your UserRepository called findUserAndCoupons($email) and have your query there. Whenever you need to find a user and his coupons, you could simply retrieve it in your controller using:
class MyController extends Controller {
public function myAction(){
$user = $this->getDoctrine()->getRepository('UserRepository')->findUserAndCoupons($email);
foreach($user->getCoupons() as $coupon) {
// ....
}
}
}
This way you won't need to remember the actual query and copy/paste it all over the place. :)

Symfony2 - How to Dynamically get a Doctrine entity's Entity Manager

We are building a CMS and website building platform with a lot of different vendors and clients sites, so there are a lot of different content type entities, which are edited by generic controllers and helper services that don't necessarily (easily) know what the entity manager is for a given entity.
NOTE: We have several entityManagers to separate access to different databases, e.g. Global, Billing, Local, etc
There are many cases where we need to detect what is the entity's EntityManager. For example, we have a MediaHelper that dynamically associates media from a database with matching fields on the entity (this doesn't work with associations because the Media has to connect with literally any entity and you can't have that kind of dynamic association and we don't want a hundred different associations).
The media is in a bundle managed by the 'Local' EntityManager. But the entity may be in a 'Global' EntityManager (you can't assume it's in the same entity manager). So we need to detect and persist the right entity manager for the right entity.
So how do you recommend dynamically detecting the entityManager for an entity?
Original Custom Method
NOTE: the accepted answer is a much better solution. This is just here for archival purposes.
Here is a simple solution that works. But I don't know enough about Symfony and Doctrine to know if it's a bad idea? Does anyone else know? If not, I don't know why this wouldn't be in the core, as a Doctrine Utility.
I created an EntityHelper service that injects the Doctrine service into it:
gutensite_cms.entity_helper:
class: Gutensite\CmsBundle\Service\EntityHelper
arguments:
- "#doctrine"
Then in the entity helper is one simple function to get the Entity Manager for an entity (the config.yml registers the bundles for entity managers already):
/**
* Automagically find the entityManager for an entity.
* #param $entity
* #return mixed
*/
public function getManagerForEntity($entity) {
$className = \Doctrine\Common\Util\ClassUtils::getRealClass(get_class($entity));
foreach (array_keys($this->doctrine->getManagers()) as $name) {
if(in_array($className, $this->doctrine->getManager($name)->getConfiguration()->getMetadataDriverImpl()->getAllClassNames())) return $em;
}
}
NOTE: Doctrine Registry#getAliasNamespace already does something nearly identical to this foreach loop, I just modified the idea to return the entity manager instead of the namespace, e.g.
public function getAliasNamespace($alias) {
foreach (array_keys($this->getManagers()) as $name) {
try {
return $this->getManager($name)->getConfiguration()->getEntityNamespace($alias);
} catch (ORMException $e) {
}
}
throw ORMException::unknownEntityNamespace($alias);
}
Update 10/21/15: Entity Detection Code updated per #Cerad's suggestion.
Per the suggestion of #qooplmao, there is an easy method already in the Doctrine core.
// 1) get the real class for the entity with the Doctrine Utility.
$class = \Doctrine\Common\Util\ClassUtils::getRealClass(get_class($entity))
// 2) get the manager for that class.
$entityManager = $this->container->get('doctrine')->getManagerForClass($class);
Updated 10/22/15 After suggestions from Cerad and Qooplmao
Can you not give the entity manager a alias that suits the context it is for? You talk about Global, Billing, Local, so for example:
'service_manager' => array(
'aliases' => array(
'global_entity_manager' => 'My\Global\EntityManager',
'billing_entity_manager' => 'My\Billing\EntityManager',
'local_entity_manager' => 'My\Local\EntityManager',
),
)
You could also map the entity manager to the namespace of the entity.
So let's say you have a folder for your Global entities that is Global\Entity, then you could alias the entity manager for those entities Global\Entity\EntityManager. The advantage of this solution is that you can map several namespaces to the same entity manager, so your Billing and Global entities can easily share the same entity manager:
'service_manager' => array(
'aliases' => array(
'Global\Entity\EntityManager' => 'My\Global\EntityManager',
'Billing\Entity\EntityManager' => 'My\Global\EntityManager', // <-same name
'Local\Entity\EntityManager' => 'My\Local\EntityManager',
),
)
This only works if your entities in one namespace are manager by the same EntityManager instance. I can hardly believe this would not be the case in any project, but otherwise you should maybe reorganize a bit? :D

whats the point of using the #Method annotation

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.

Resources