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
Related
I want to create HelperController for my project. I generate a controller with doctrine:generate:controller and I need to use entity manager in it.
I enjected to services.yml but it is giving an error like this:
Argument 1 passed to CampingBundle\Controller\HelperController::__construct() must be an instance of Doctrine\ORM\EntityManager, none given ...
My Controller Code :
namespace CampingBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Doctrine\ORM\EntityManager;
class HelperController extends Controller
{
protected $manager;
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
My Services.yml :
services:
camping.helper_controller:
class: CampingBundle\Controller\HelperController
arguments: ["#doctrine.orm.entity_manager"]
Why it doesn't work ? Shoudl I clear cache or something else or is there anything wrong in definition ?
Thanks
Try to use EntityManagerInterface and remove extends Controller.
Check this link if you need CAS (Controllers as Services).
Change protected $manager; to private $manager;
namespace CampingBundle\Controller;
use Doctrine\ORM\EntityManagerInterface;
class HelperController
{
/**
* #var EntityManagerInterface $entityManager
*/
private $entityManager;
/**
* #param $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}
I'll leave my two cents here as I had same issue and fixed it by adding tags to service.
something.validate.some_service:
class: Path\To\Some\Validator
arguments:
- '#doctrine.orm.entity_manager'
tags:
- { name: validator.constraint_validator, alias: some_validator_alias }
How to Work with Service Tags by Symfony
I'm building my first serious Symfony2 project. I'm extending the FOSUserBundle for my user/group management, and I'd like new users to be automatically added to a default group.
I guess you just have to extend the User entity constructor like this :
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
// Get $defaultGroup entity somehow ???
...
// Add that group entity to my new user :
$this->addGroup($defaultGroup);
}
But my question is how do I get my $defaultGroup entity in the first place?
I tried using the entity manager from within the entity, but then I realized it was stupid, and Symfony was throwing an error. I googled for this, but found no real solution except maybe setting up a service for that... although this seems quite unclear for me.
OK, I started working on implementing artworkad's idea.
First thing I did was updating FOSUserBundle to 2.0.*#dev in composer.json, because I was using v1.3.1, which doesn't implement the FOSUserEvents class. This is required to subscribe to my registration event.
// composer.json
"friendsofsymfony/user-bundle": "2.0.*#dev",
Then I added a new service :
<!-- Moskito/Bundle/UserBundle/Resources/config/services.xml -->
<service id="moskito_bundle_user.user_creation" class="Moskito\Bundle\UserBundle\EventListener\UserCreationListener">
<tag name="kernel.event_subscriber" alias="moskito_user_creation_listener" />
<argument type="service" id="doctrine.orm.entity_manager"/>
</service>
In the XML, I told the service I needed access to Doctrine through an argument doctrine.orm.entity_manager. Then, I created the Listener :
// Moskito/Bundle/UserBundle/EventListener/UserCreationListener.php
<?php
namespace Moskito\Bundle\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\EntityManager;
/**
* Listener responsible to change the redirection at the end of the password resetting
*/
class UserCreationListener implements EventSubscriberInterface
{
protected $em;
protected $user;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$this->user = $event->getForm()->getData();
$group_name = 'my_default_group_name';
$entity = $this->em->getRepository('MoskitoUserBundle:Group')->findOneByName($group_name); // You could do that by Id, too
$this->user->addGroup($entity);
$this->em->flush();
}
}
And basically, that's it !
After each registration success, onRegistrationSuccess() is called, so I get the user through the FormEvent $event and add it to my default group, which I get through Doctrine.
You did not say how your users are created. When some admin creates the users or you have a custom registration action, you can set the group in the controller's action.
$user->addGroup($em->getRepository('...')->find($group_id));
However if you use fosuserbundles build in registration you have to hook into the controllers: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/controller_events.md and use a event listener.
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);
}
}
}
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.
I'm writing an open source application uses some Symfony components, and using Symfony Console component for interacting with shell.
But, i need to inject dependencies (used in all commands) something like Logger, Config object, Yaml parsers.. I solved this problem with extending Symfony\Component\Console\Command\Command class. But this makes unit testing harder and not looks correct way.
How can i solve this ?
Since Symfony 4.2 the ContainerAwareCommand is deprecated. Use the DI instead.
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Doctrine\ORM\EntityManagerInterface;
final class YourCommand extends Command
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// YOUR CODE
$this->entityManager->persist($object1);
}
}
It is best not to inject the container itself but to inject services from the container into your object. If you're using Symfony2's container, then you can do something like this:
MyBundle/Resources/config/services (or wherever you decide to put this file):
...
<services>
<service id="mybundle.command.somecommand" class="MyBundle\Command\SomeCommand">
<call method="setSomeService">
<argument type="service" id="some_service_id" />
</call>
</service>
</services>
...
Then your command class should look like this:
<?php
namespace MyBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use The\Class\Of\The\Service\I\Wanted\Injected;
class SomeCommand extends Command
{
protected $someService;
public function setSomeService(Injected $someService)
{
$this->someService = $someService;
}
...
I know you said you're not using the dependency injection container, but in order to implement the above answer from #ramon, you have to use it. At least this way your command can be properly unit tested.
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Extends your Command class from ContainerAwareCommand and get the service with $this->getContainer()->get('my_service_id');
You can use ContainerCommandLoader in order to provide a PSR-11 container as follow:
require 'vendor/autoload.php';
$application = new Application('my-app', '1.0');
$container = require 'config/container.php';
// Lazy load command with container
$commandLoader = new ContainerCommandLoader($container, [
'app:change-mode' => ChangeMode::class,
'app:generate-logs' => GenerateLogos::class,
]);
$application->setCommandLoader($commandLoader);
$application->run();
ChangeMode class could be defined as follow:
class ChangeMode extends Command
{
protected static $defaultName = 'app:change-mode';
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
parent::__construct(static::$defaultName);
}
...
NB.: ChangeMode should be provided in the Container configuration.
I'm speaking for symfony2.8. You cannot add a constructor to the class that extends the ContainerAwareCommand because the extended class has a $this->getContainer() which got you covered in getting your services instead of injecting them via the constructor.
You can do $this->getContainer()->get('service-name');
Go to services.yaml
Add This to the file(I used 2 existing services as an example):
App\Command\MyCommand:
arguments: [
'#request_stack',
'#doctrine.orm.entity_manager'
]
To see a list of all services type in terminal at the root project folder:
php bin/console debug:autowiring --all
You will get a long list of services you can use, an example of one line would look like this:
Stores CSRF tokens.
Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface (security.csrf.token_storage)
So if CSRF token services is what you are looking for(for example) you will use as a service the part in the parenthesis: (security.csrf.token_storage)
So your services.yaml will look somewhat like this:
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
# Here might be some other services...
App\Command\MyCommand:
arguments: [
'#security.csrf.token_storage'
]
Then in your command class use the service in the constructor:
class MyCommand extends Command
{
private $csrfToken;
public function __construct(CsrfToken $csrfToken)
{
parent::__construct();
$this->csrfToken = $csrfToken;
}
}
In Symfony 3.4, if autowire is configured correctly, services can be injected into the constructor of the command.
public function __construct(
\AppBundle\Handler\Service\AwsS3Handler $s3Handler
) {
parent::__construct();
$this->s3Handler = $s3Handler;
}
php 8.1
Symfony 6.1
public function __construct(private EntityManagerInterface $em, string $name = null)
{
parent::__construct($name);
}