To use different Entity Manager / Connection based on URL in Symfony if fairly easy. With the following routing configuration
connection:
pattern: /a/{connection}
defaults: { _controller: AcmeTestBundle:User:index }
and from the following Cookbook;
How to work with Multiple Entity Managers and Connections
My controller would look something like this;
class UserController extends Controller
{
public function indexAction($connection)
{
$products = $this->get('doctrine')
->getRepository('AcmeStoreBundle:Product', $connection)
->findAll()
;
..................
and I'll be able to fetch product information from different em/connection/database.
Now, if I add something like this to my routing;
login:
pattern: /a/{connection}/login
defaults: { _controller: FOSUserBundle:Security:login }
How can I easily make the login to use connection as defined in the connection variable?
This setup assume each database has their own user login information (the fos_user table).
Edit: Updated routing information
Edit2:
I'm still new with PHP/Symfony/Doctrine though, so please forgive me if I'm completely wrong here. I tried to manually set the connection at FOS\UserBundle\Doctrine\UserManager. The following is the constructor of the class
//
use Doctrine\Common\Persistence\ObjectManager;
//
public function __construct(EncoderFactoryInterface $encoderFactory, CanonicalizerInterface $usernameCanonicalizer, CanonicalizerInterface $emailCanonicalizer, ObjectManager $om, $class)
{
parent::__construct($encoderFactory, $usernameCanonicalizer, $emailCanonicalizer);
$this->objectManager = $om;
$this->repository = $om->getRepository($class);
$metadata = $om->getClassMetadata($class);
$this->class = $metadata->getName();
}
In a controller, we can use the following method to change the em to 'testing'
$em = $this->get('doctrine')->getManager('testing');
$repository = $this->get('doctrine')->getRepository($class, 'testing')
For that I changed the code to the following to use EntityManager instead of ObjectManager.
//
//use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
//
public function __construct(EncoderFactoryInterface $encoderFactory, CanonicalizerInterface $usernameCanonicalizer, CanonicalizerInterface $emailCanonicalizer, EntityManager $om, $class)
{
parent::__construct($encoderFactory, $usernameCanonicalizer, $emailCanonicalizer);
$this->objectManager = $om;
$this->repository = $om->getRepository($class);
$metadata = $om->getClassMetadata($class);
$this->class = $metadata->getName();
}
My app works fine with no error.
From the way it works with the controller, I tried changing the connection by adding a parameter to this line then, but it's still using the default connection.
$this->repository = $om->getRepository($class, 'testing');
What else could I be missing here?
As you can see, FOSUserBundle can have only one EntityManager. You can see it from the settings orm.xml
<service id="fos_user.entity_manager" factory-service="doctrine" factory-method="getManager" class="Doctrine\ORM\EntityManager" public="false">
<argument>%fos_user.model_manager_name%</argument>
</service>
Parameter %fos_user.model_manager_name% specified in settings as model_manager_name
fos_user:
db_driver: ~ # Required
user_class: ~ # Required
firewall_name: ~ # Required
model_manager_name: ~
So into the constructor comes the instance of EntityManager, which does not accept the second parameter in the getRepository. Therefore, the standard FOSUserBundle can only work with one database.
But this is not the end of story, it's Symfony :)
We can write out UserManager, that can use different db connections. In the setting see that fos_user.user_manager is a fos_user.user_manager.default. We find it in orm.xml
<service id="fos_user.user_manager.default" class="FOS\UserBundle\Doctrine\UserManager" public="false">
<argument type="service" id="security.encoder_factory" />
<argument type="service" id="fos_user.util.username_canonicalizer" />
<argument type="service" id="fos_user.util.email_canonicalizer" />
<argument type="service" id="fos_user.entity_manager" />
<argument>%fos_user.model.user.class%</argument>
</service>
We can override this class to add an additional parameter that will determine what kind of connection you want to use. Further by ManagerFactory you can get the desired ObjectManager. I wrote simple example for the two databeses (if you need more databases you can write your factory for this service)
define your services in services.yml
services:
acme.user_manager.conn1:
class: Acme\DemoBundle\Service\UserManager
public: true
arguments:
- #security.encoder_factory
- #fos_user.util.username_canonicalizer
- #fos_user.util.email_canonicalizer
- #doctrine
- 'conn1_manager'
- %fos_user.model.user.class%
acme.user_manager.conn2:
class: Acme\DemoBundle\Service\UserManager
public: true
arguments:
- #security.encoder_factory
- #fos_user.util.username_canonicalizer
- #fos_user.util.email_canonicalizer
- #doctrine
- 'conn2_manager'
- %fos_user.model.user.class%
Your manager
/**
* Constructor.
*
* #param EncoderFactoryInterface $encoderFactory
* #param CanonicalizerInterface $usernameCanonicalizer
* #param CanonicalizerInterface $emailCanonicalizer
* #param RegistryInterface $doctrine
* #param string $connName
* #param string $class
*/
public function __construct(EncoderFactoryInterface $encoderFactory, CanonicalizerInterface $usernameCanonicalizer,
CanonicalizerInterface $emailCanonicalizer, RegistryInterface $doctrine, $connName, $class)
{
$om = $doctrine->getEntityManager($connName);
parent::__construct($encoderFactory, $usernameCanonicalizer, $emailCanonicalizer, $om, $class);
}
/**
* Just for test
* #return EntityManager
*/
public function getOM()
{
return $this->objectManager;
}
and simple test
/**
* phpunit -c app/ src/Acme/DemoBundle/Tests/FOSUser/FOSUserMultiConnection.php
*/
class FOSUserMultiConnection extends WebTestCase
{
public function test1()
{
$client = static::createClient();
/** #var $user_manager_conn1 UserManager */
$user_manager_conn1 = $client->getContainer()->get('acme.user_manager.conn1');
/** #var $user_manager_conn2 UserManager */
$user_manager_conn2 = $client->getContainer()->get('acme.user_manager.conn2');
/** #var $om1 EntityManager */
$om1 = $user_manager_conn1->getOM();
/** #var $om2 EntityManager */
$om2 = $user_manager_conn2->getOM();
$this->assertNotEquals($om1->getConnection()->getDatabase(), $om2->getConnection()->getDatabase());
}
}
I'm sorry that the answer was so big. If something is not clear to the end, I put the code on github
FosUserBundle is not able to have more than one entity manager.
The easiest way I found to use 2 databases, is to override the 'checkLoginAction' of the SecurityController.
<?php
//in myuserBunle/Controller/SecurityController.php
class SecurityController extends BaseController
{
/**
* check the user information
*/
public function checkLoginAction(Request $request){
$username = \trim($request->request->get("_username"));
$user = $this->container->get('fos_user.user_manager')->findUserByUsername($username);
$userDB2 = .....
$password = \trim($request->request->get('_password'));
if ($user) {
// Get the encoder for the users password
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user);
$encoded_pass = $encoder->encodePassword($password, $user->getSalt());
if (($user->getPassword() == $encoded_pass) || $this->checkSecondEM()) {
$this->logUser($request, $user);
return new RedirectResponse($this->container->get('router')->generate($this->container->get('session')->get('route'), $request->query->all() ));
} else {
// Password bad
return parent::loginAction($request);
}
} else {
// Username bad
return parent::loginAction($request);
}
}
}
Related
I am trying to override the FosUserBundle Registry Controller, but the following error appears:
Cannot autowire service "App\Controller\RegistrationController": argument "$formFactory" of method
"FOS\UserBundle\Controller\RegistrationController::__construct()"
references interface "FOS\UserBundle\Form\Factory\FactoryInterface" but no such service exists.
You should maybe alias this interface to one of these existing services: "fos_user.profile.form.factory", "fos_user.registration.form.factory", "fos_user.change_password.form.factory",
"fos_user.resetting.form.factory". Did you create a class that implements this interface?
I'm using Symfony4 and this is my RegistrationController.php code.
I've tried multiple ways, but I can't find a way to make it work.
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
$form = $this->get('fos_user.registration.form.factory');
$formHandler = $this->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->getParameter('fos_user.registration.confirmation.enabled');
die("Hello");
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
if ($confirmationEnabled) {
$this->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
// $this->authenticateUser($user);
$route = 'users_edit';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$url = $this->get('router')->generate($route, array("id" => $user->id));
return new RedirectResponse($url);
}
return $this->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.twig', array(
'form' => $form->createView()
));
}
}
?>
I had the same problem , with SF4 and FOSUserBundle. After looking around for half a day I come with a solution. I use some material from #thibaut and adapt it to SF 4.2.
I inject the services needed by fos user registration controlleur and give FOS\UserBundle\Form\Factory\FactoryInterface an alias
config/services.yaml
app.controller.register:
class: App\Controller\bundles\FOSUserBundle\RegistrationController
arguments:
$eventDispatcher: '#event_dispatcher'
$formFactory: '#fos_user.registration.form.factory'
$userManager: '#fos_user.user_manager'
$tokenStorage: 'security.token_storage'
calls:
- method: setContainer
arguments:
- '#service_container'
public: true
FOS\UserBundle\Form\Factory\FactoryInterface:
alias: 'fos_user.registration.form.factory'
public: true
Then in my registration controller, I redeclare a constructor and save formfactory to be use in the controller
App\Controller\bundles\FOSUserBundle
protected $formFactory;
public function __construct(EventDispatcherInterface $eventDispatcher, FactoryInterface $formFactory, UserManagerInterface $userManager, TokenStorageInterface $tokenStorage, $serviceContainer=null)
{
$this->setContainer($serviceContainer);
$this->formFactory = $formFactory;
parent::__construct($eventDispatcher, $formFactory, $userManager, $tokenStorage);
}
use the declare $formfactory in my action
/**
* #Route("/register", name="registration")
*/
public function registerAction(Request $request)
{
/** #var $formFactory FactoryInterface */
$formFactory = $this->formFactory;
/** #var $userManager UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
/** #var $dispatcher EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
Hope it also help you guys
work in symfony 4.2
services.yaml
App\Controller\RegistrationController:
arguments:
$formFactory: '#fos_user.registration.form.factory'
and controller
use FOS\UserBundle\Controller\RegistrationController as Base;
class RegistrationController extends Base
{
private $eventDispatcher;
private $formFactory;
private $userManager;
private $tokenStorage;
public function __construct(EventDispatcherInterface $eventDispatcher, FactoryInterface $formFactory, UserManagerInterface $userManager, TokenStorageInterface $tokenStorage)
{
parent::__construct($eventDispatcher, $formFactory,$userManager, $tokenStorage);
$this->eventDispatcher = $eventDispatcher;
$this->formFactory = $formFactory;
$this->userManager = $userManager;
$this->tokenStorage = $tokenStorage;
}
I don't know which version of FOSUserBundle are you using but there is no official support for sf4 yet - see release notes. You can try dev-master version and follow this issue to make it work.
You should use hooks and avoid override controllers in FOSUserBundel v2.x
More information you can read on the page: http://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html
I had to use an alias for the FOS services that were refusing to autowire in my config/services.yaml
FOS\UserBundle\Form\Factory\FormFactory: '#fos_user.registration.form.factory'
As seen in vendor/friendsofsymfony/user-bundle/Resources/config/registration.xml
Hello i think i have found a way to handle this problem:
Define your service:
app.controller.register:
class: AppBundle\Controller\RegisterController
arguments: ['#service_container']
public: true
then your controller
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use FOS\UserBundle\Controller\RegistrationController as BaseController;
/**
* #Route(service="app.controller.register")
*/
class RegisterController extends BaseController
{
public function __construct($serviceContainer=null)
{
$this->setContainer($serviceContainer);
$eventDispatcher=$this->container->get('event_dispatcher');
$formFactory=$this->container->get('fos_user.registration.form.factory');
$userManager=$this->container->get('fos_user.user_manager');
$tokenStorage=$this->container->get('security.token_storage');
parent::__construct($eventDispatcher, $formFactory, $userManager, $tokenStorage);
}
/**
* #Route("/register", name="register")
*/
public function registerAction(Request $request)
{
//do your stuff
}
}
hope this help (sorry for bad english)
To solve this issue in an override:
Do a full override of the FOSUserBundle RegistrationController i.e. copy its entire contents to AppBundle/Controller/RegistrationController.php
Then replace the __construct function with the below:
public function __construct() {
$this->eventDispatcher = $this->get('event_dispatcher');
$this->formFactory = $this->get('fos_user.registration.form.factory');
$this->userManager = $this->get('fos_user.user_manager');
$this->tokenStorage = $this->get('security.token_storage'); }
Voila! You are just calling these services in the constructor instead of passing them in as parameters in a registration controller service.
I did this now & it works for sf3.4.
I had the same problem , with SF4, FOSUserBundle (behind SonataAdmin).
I wanted listen an event : Registration confirmed, to send a message to the admininstrator.
my first try : override the FosUser controller. But the issues are many !
Thanks to #majne, I went to http://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html, and, even I am not in 'master', that works, hooking the event, from FOS\UserBundle\FOSUserEvents class.
I had some problems to cleary adapt the use statements...
With Symfony 4.2 the following should work.
Contrary to what is suggested in other answers, there was no need to extend the corresponding FOSUserBundle controller or modify the __construct method.
/config/services.yaml
FOS\UserBundle\Form\Factory\FactoryInterface: '#fos_user.registration.form.factory'
In my case I actually overrode both the RegistrationController and the ResettingController and in that case one needs to wire both forms - and also the MailerInterface - as so:
/config/services.yaml
FOS\UserBundle\Form\Factory\FactoryInterface: '#fos_user.resetting.form.factory'
FOS\UserBundle\Mailer\MailerInterface: '#fos_user.mailer.default'
App\Controller\RegistrationController:
autowire: true
arguments:
$formFactory: '#fos_user.registration.form.factory'
I had the same problem and I solve it by making "fos_user.resetting.form.factory" public
#fos_user.yaml
services:
new_service_name:
alias: fos_user.registration.form.factory
public: true
Then use the service with the new name
$form = $this->get('new_service_name');
I'm using the Gedmo SoftDeletable filter for Symfony2 and Doctrine (https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/softdeleteable.md)
I'm also using the JMSSerializerBundle to serialize reponses to JSON for my REST API.
How to tell jms serializer annotation group about softdeleteable filters ?
And when my response contain entity which have relation with entity which have deleted_at field not empty I have error
Entity of type 'AppBundle\Entity\Courses' for IDs id(2) was not found
because sub_cources, example id 1 have relation with courses in example id 2 and courses with id 2 have not empty deleted_at - removed entity
example I have
/**
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="sub_cources")
*#ORM\Entity(repositoryClass="AppBundle\Entity\Repository\SubCoursesRepository")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
* #AssertBridge\UniqueEntity(
* groups={"post_sub_course", "put_sub_course"},
* fields="name",
* errorPath="not valid",
* message="This name is already in use."
* )
*/
class SubCourses
/**
* #var Courses
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Courses", inversedBy="subCourses")
* #Annotation\Groups({
* "get_sub_courses"
* })
* #Annotation\Type("AppBundle\Entity\Courses")
*/
private $courses;
my action
return $this->createSuccessResponse(
[
'sub_courses' => $subCourses->getEntitiesByParams($paramFetcher),
'total' => $subCourses->getEntitiesByParams($paramFetcher, true),
],
['get_sub_courses'],
true
);
And my response look like
/**
* #param $data
* #param null|array $groups
* #param null|bool $withEmptyField
*
* #return View
*/
protected function createSuccessResponse($data, array $groups = null, $withEmptyField = null)
{
$context = SerializationContext::create()->enableMaxDepthChecks();
if ($groups) {
$context->setGroups($groups);
}
if ($withEmptyField) {
$context->setSerializeNull(true);
}
return View::create()
->setStatusCode(self::HTTP_STATUS_CODE_OK)
->setData($data)
->setSerializationContext($context);
}
How to tell jms serializer annotation group about softdeleteable filters ?
The solution to this issue that I used was to use the serializer events for pre and post serialization.
The first thing I did was have my entity implement the \Gedmo\SoftDeleteable\SoftDeleteable interface.
I created a container-aware listener and on the pre serialize event, checked to see if the object was soft-deleteable. if it was, then I disabled the soft deletable filter. In post serialization, I make sure the filter is turned back on.
This only works for lazy-loaded/proxy relationships. if your relationship fetch is set to EAGER, this will NOT work
Subscriber class:
<?php
namespace AppBundle\Event\Subscriber;
use Gedmo\SoftDeleteable\SoftDeleteable;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface as JmsEventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
class JsonSerializerSubscriber implements JmsEventSubscriberInterface, EventSubscriberInterface
{
/**
* #var ContainerInterface
*/
protected $container;
public static function getSubscribedEvents()
{
return [
[
'event' => 'serializer.pre_serialize',
'method' => 'onPreSerialize',
],
[
'event' => 'serializer.post_serialize',
'method' => 'onPostSerialize',
],
];
}
public function onPreSerialize(ObjectEvent $objectEvent)
{
// before serializing; turn off soft-deleteable
$object = $objectEvent->getObject();
if ($object instanceof SoftDeleteable) {
$em = $this->container->get('doctrine.orm.default_entity_manager');
if ($em->getFilters()->isEnabled('softdeleteable')) {
$em->getFilters()->disable('softdeleteable');
}
}
}
public function onPostSerialize(ObjectEvent $objectEvent)
{
// after serializing; make sure that softdeletable filter is turned back on
$em = $this->container->get('doctrine.orm.default_entity_manager');
if (!$em->getFilters()->isEnabled('softdeleteable')) {
$em->getFilters()->enable('softdeleteable');
}
}
/**
* #param ContainerInterface $container
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
}
}
services.xml:
<services>
...
<service id="my.serializer.event_subscriber" class="AppBundle\Event\Subscriber\JsonSerializerSubscriber">
<call method="setContainer">
<argument type="service" id="service_container"/>
</call>
<tag name="jms_serializer.event_subscriber" />
</service>
...
</services>
Entity class (using op's example):
class SubCourses implements \Gedmo\SoftDeleteable\SoftDeleteable
{
...
}
I'm trying to set up HWIOAuthBundle to work with FOSUserBundle.
While making my own User Provider that extends FOSUBUserProvider, I did the following:
namespace Naroga\Reader\CommonBundle\Service\Security;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
class NarogaUserProvider extends FOSUBUserProvider {
public function loadUserByOAuthUserResponse(UserResponseInterface $response) {
[...]
}
}
My services.yml is as follows:
naroga.reader.common.security.user_provider:
class: Naroga\Reader\CommonBundle\Service\Security\NarogaUserProvider
arguments: [ #fos_user.user_manager ]
Whenever I run the program, I get the following error:
Argument 2 passed to HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider::__construct() must be of the type array, none given, called in
This makes great sense, because FOSUBUserProvider::__construct's signature is public function __construct(UserManagerInterface $userManager, array $properties).
I have no idea what to define as being my second parameter to my service so it can override FOSUBUserProvider. I've been googling it and all I find is people with the same question, no answers.
I'd be forever grateful to the gentle soul that tells me what the second parameter must be in order to comply with FOSUBUserProvider's signature.
Thank you.
The second argument is used for mapping.
As I understand, this is the name corresponding OAuth service and name of field in your entity.
For example, you can send them as follows:
naroga.reader.common.security.user_provider:
class: Naroga\Reader\CommonBundle\Service\Security\NarogaUserProvider
arguments: [ #fos_user.user_manager, { facebook: facebookId, twitter: twitterId } ]
In case you would like to pass also some additional arguments, let's say fire an event using dispatcher which was my case, just overwrite the constructor.
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
FOSUBUserProvider extends BaseClass
{
/**
* #var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* Constructor.
*
* #param UserManagerInterface $userManager FOSUB user provider.
* #param array $properties Property mapping.
* #param EventDispatcherInterface $eventDispatcher
*/
public function __construct(
UserManagerInterface $userManager,
array $properties,
EventDispatcherInterface $eventDispatcher)
{
$this->userManager = $userManager;
$this->properties = array_merge($this->properties, $properties);
$this->accessor = PropertyAccess::createPropertyAccessor();
$this->eventDispatcher = $eventDispatcher;
}
And the service definition:
tb_user_provider:
class: "%tb_user_provider.class%"
arguments: [#fos_user.user_manager, { facebook: facebook_id }, #event_dispatcher]
We have service as
services:
service_name:
class: One\SomeBundle\Controller\OurController
What need we add, to make $this->getDoctrine() works in our "OurController" ?
I tried in "OurController"
$this->countainer->get('doctrine')
// and
$this->getDoctrine()
but nothing happens, just errors.
I want that this controller use native methods such as $this->getDoctrine(). As I know we can give arguments to controller(arguments in services.yml), and then apply it, but can we set it as default? Without
function __construct($em)
{
$this->em = $em;
}
and other additional stuff. All i need is to make $this->getDocrine() works.
i think the doctrine is avaiable in all controllers like this
$em = $this->getDoctrine()->getEntityManager();
if you want the doctrine available in your service then use something like this
services:
your.service:
class: YourVendor\YourBundle\Service\YourService
arguments: [ #doctrine.orm.entity_manager ]
When you define a service, you have to pass parameters as arguments as follow (since a service doesn't have access to the main container by default):
<services>
<service id="myservice" class="path/to/my/class">
<argument type="service" id="doctrine" />
...
</service>
</services>
This is configured in xml but I'll let you convert it in yml if you like.
Then in your service class you just need to set your constructor as so:
class MyServiceClass
{
protected $doctrine;
public function __construct(\Doctrine $doctrine, ...)
{
$this->doctrine = $doctrine;
....
}
}
Now the doctrine service will be available in your own service Class.
And your can usage JMSDiExtra for set services to properties in Controller:
Controller code:
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use JMS\DiExtraBundle\Annotation as DI;
/**
* Controller class
*/
class MyController extends Controller
{
/**
* #DI\Inject
*/
protected $request;
/**
* #DI\Inject("doctrine.orm.entity_manager")
*/
protected $em;
// .....
/**
* Action
*/
public function myAction()
{
// ....
$this->em->persist($myObject);
// ....
}
}
More documentation with JMSDiExtra - http://jmsyst.com/bundles/JMSDiExtraBundle
This bundle is a default in Symfony2 Framework
Would be possible to have a custom repository not associated with an entity in Symfony 2 and Doctrine 2? I would like to put in it some native SQL that doesn't fit well in other repositories (it may refer to abstract or entity hierarchy).
How controller code $this->getDoctrine()->getRepositoty(/* ??? */) should be replaced?
It's possible to have as many repositories as you wish. However, only a single repository can be linked with the entity manager.
You need to define a few services to add a custom repository.
<!-- My custom repository -->
<service id="acme.repository.my_entity" class="Acme\FQCN\MyEntityRepository" >
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="acme.metadata.my_entity" />
</service>
<!-- MyEntity metadata -->
<service id="acme.metadata.my_entity" class="Doctrine\ORM\Mapping\ClassMetaData">
<argument>Acme\FQCN\MyEntity</argument>
</service>
The repository class would have to inherit from EntityRepository.
namespace Acme\FQCN;
use Doctrine\ORM\EntityRepository;
class MyEntityRepository extends EntityRepository
{
/**
* If you want to inject any custom dependencies, you'd have either have to
* add them to the construct or create setters. I'd suggest using setters
* in which case you wouldn't need to use the constructor in this class.
*
* public function __construct($em, Doctrine\ORM\Mapping\ClassMetadata $class, $custom_dependency)
* {
* parent::__construct($em, $class);
* }
*
*/
}
Unfortunately you'll not be able to retrieve it via the doctrine service. Instead, retrieve it straight from the container:
$this->get('acme.repository.my_entity');
EDIT
If you're creating a repository that shouldn't be linked to any entities, simply create a service and inject the necessary dependencies.
<!-- Repository for misc queries -->
<service id="acme.repository.misc" class="Acme\FQCN\MiscRepsitory">
<argument type="service" id="database_connection" />
</service>
Since you're not using any of the Doctrine's ORM features in a custom repository, there's no need to extend EntityManager.
namespace Acme\FQCN;
use \Doctrine\DBAL\Connection;
class MiscRepository
{
protected $conn;
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
}
I adopted a slightly different solution using Symfony2 parent services.
First of all I created a parent service, a GenericRepository class that exposes a couple of methods and makes life easier in case we'd like to refactor our code in the future.
services.yml
acme_core.generic_repository:
abstract: true
class: Acme\Bundle\CoreBundle\Repository\GenericRepository
arguments: [#doctrine.orm.entity_manager]
Acme\Bundle\CoreBundle\Repository\GenericRepository
<?php
namespace Acme\Bundle\CoreBundle\Repository;
use Doctrine\ORM\EntityManager;
/**
* Class GenericRepository
* #package Acme\Bundle\CoreBundle\Repository
*/
abstract class GenericRepository {
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager) {
$this->entityManager = $entityManager;
}
/**
* #return EntityManager
*/
public function getEntityManager() {
return $this->entityManager;
}
/**
* #return \Doctrine\DBAL\Connection
*/
public function getConnection() {
return $this->getEntityManager()->getConnection();
}
/**
* #return string
*/
abstract function getTable();
}
Now we want to define a new repository:
services.yml
# Repositories
acme_product.repository.product_batch:
parent: acme_core.generic_repository
class: Acme\Bundle\ProductBundle\Repository\ProductBatchRepository
Acme\Bundle\ProductBundle\Repository\ProductBatchRepository
<?php
namespace Acme\Bundle\ProductBundle\Repository;
use Acme\Bundle\CoreBundle\Repository\GenericRepository;
/**
* Class ProductBatchRepository
* #package Acme\Bundle\ProductBundle\Repository
*/
class ProductBatchRepository extends GenericRepository {
/**
* #param int $batchId
* #return integer The number of affected rows.
*/
public function deleteBatch($batchId) {
$table = $this->getTable();
return $this->getConnection()->delete($table, [
'id' => $batchId
]);
}
/**
* {#inheritdoc}
*/
public function getTable() {
return 'product_batch';
}
}
The deleteBatch() method creates and executes the following query:
DELETE FROM product_batch WHERE id = ?
Finally in our controller:
public function deleteAction() {
$batchId = $this->getRequest()->get('batchId');
$affectedRows = $this->get('acme_product.repository.product_batch')->deleteBatch($batchId);
return $this->render(/**/);
}
For further information and entity manager / connection usage please refer to the official documentation: http://doctrine-orm.readthedocs.org/en/latest/reference/native-sql.html
My suggestion is to create a plain PHP class with the needed dependencies in the constructor and get it through the service container.