Is it a good practice to have a service getter for frequently used services in a controller? For example I mean:
class SomeController Extends Contorller {
private function getSomethingManager()
{
return $this->get('myvendorname.something.manager');
}
}
Your example is a bit confusing because you can use the Doctrine service directly with your controller. You can inject it in your Action if you use the Autowire function.
public function test(EntityManagerInterface $em) {
}
Then you have the entity manager injected or you can load it over the controller with:
$this->getDoctrine()->getManager()
So this is not a real good example. When you use autowire all classes are registered as service and you can use it.
For database queries you have to use entities and repositories.
https://symfony.com/doc/current/doctrine.html
If you are above Symfony 3.3 you can use a Service Locater. You list all common services in Service Locator class. When you need to fetch a specific service from anywhere (from example, Controller, Command, Service so on), all you have to do is, inject ServiceLocator class and fetch required service via ServiceLocator:locate.
It is pretty simple and useful. It helps you to reduce dependency injection as well. Have a look at the full example in the link above.
class ServiceLocator implements ServiceLocatorInterface, ServiceSubscriberInterface
{
private $locator;
public function __construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public static function getSubscribedServices()
{
return [
ModelFactoryInterface::class,
CalculatorUtilInterface::class,
EntityManagerInterface::class,
AnotherClass::class,
AndAnother::class,
];
}
public function get(string $id)
{
if (!$this->locator->has($id)) {
throw new ServiceLocatorException(sprintf(
'The entry for the given "%s" identifier was not found.',
$id
));
}
try {
return $this->locator->get($id);
} catch (ContainerExceptionInterface $e) {
throw new ServiceLocatorException(sprintf(
'Failed to fetch the entry for the given "%s" identifier.',
$id
));
}
}
}
And this is how you use it: ServiceLocator->locate(AnotherClass::class);
Related
Aware that there is a lot of information around the net regarding this, I am still having a lot of trouble getting this to work.
I have created a custom service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\AccommodationType;
use App\Entity\Night;
class AvailabilityChecks {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->em->getDoctrine()->getRepository(AccommodationType::class)->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->em->getDoctrine()->getRepository(Night::class)->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
and have put this in services.yaml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
So when I try and use this in my controller, I get this error:
Too few arguments to function App\Service\AvailabilityChecks::__construct(), 0 passed in /mypath/src/Controller/BookController.php on line 40 and exactly 1 expected
I just can't figure out why it's not injecting the ORM stuff into the constructor! Any help greatly appreciated
The problem is in your BookController. Even though you didn't posted its code I can assume you create new AvailabilityChecks in it (on line 40).
In Symfony every service is intantiated by service container. You should never intantiate service objects by yourself. Instead BookController must ask service container for AvailabilityChecks service. How should it do it ?
In Symfony <3.3 we used generally :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function myAction()
{
$em = $this->get('doctrine.orm.entity_manager');
// ...
}
}
Nowadays services can be injected in controllers using autowiring which is way easier:
use Doctrine\ORM\EntityManagerInterface;
class MyController extends Controller
{
public function myAction(EntityManagerInterface $em)
{
// ...
}
}
You are using the wrong service for what you want to do. The alias doctrine that is used, e.g. in the AbstractController when you call getDoctrine() is bound to the service Doctrine\Common\Persistence\ManagerRegistry.
So the code you wrote fits better with that and you should either add #doctrine or #Doctrine\Common\Persistence\ManagerRegistry to the service definition.
Both with your current configuration or the changed one, you don't have to call $this->em->getDoctrine(), because $this->em is already equivalent to $this->getDoctrine() from your controller. Instead you could create a (private) method to make it look more like that code, e.g.:
private function getDoctrine()
{
return $this->em;
}
Then you can call $this->getDoctrine()->getRepository(...) or use $this->em->getRepository(...) directly.
In Symfony 4, you dont need to create it as services. This is automatically now. Just inject the dependencies what you need in the constructor. Be sure that you have autowire property with true value in services.yml (it is by default)
Remove this from services.yml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
You dont need EntityManagerInterface because you are not persisting anything, so inject repositories only.
<?php
namespace App\Service;
use App\Entity\AccommodationType;
use App\Entity\Night;
use App\Repository\AccommodationTypeRepository;
use App\Repository\NightRepository;
class AvailabilityChecks {
private $accommodationTypeRepository;
private $nightRepository
public function __construct(
AcommodationTypeRepository $acommodationTypeRepository,
NightRepository $nightRepository
)
{
$this->acommodationTypeRepository = $acommodationTypeRepository;
$this->nightRepository = $nightRepository;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->acommodationTypeRepository->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->nightRepository->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
In SF4, you no longer need to specify dependencies required by your custom service in the service.yaml file. All you have to do is to use dependency injection.
So remove config lines, and call your service directly in the controller method :
<?php
namespace App\Controller;
use App\Service\AvailabilityChecks ;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AppController extends AbstractController
{
public function index(AvailabilityChecks $service)
{
...
}
}
Having said that, i think you don't need custom service to do simple operations on database. Use repository instead.
Calling member function of other controller in zend framework3?
You should write a mailer class and inject it in to action and send mails on it. Probably you will need mailer class in few actions so an aware trait would be nice so you will not have to inject it on every action on __construct method. I think something like that can solve problem, so you can use your mailer service in anywhere you want. Just don't forget to inject it.
interface MailServiceInterface
{
public function send(string $to, string $from, string $subject, string $body, array $headers = []);
}
trait MailServiceAwareTrait
{
/**
* #var \Infrastructure\Mailer\MailServiceInterface
*/
protected $mailService;
public function setMailService(MailServiceInterface $mailService)
{
$this->mailService = $mailService;
}
public function getMailService(): MailServiceInterface
{
return $this->mailService;
}
}
class myAction extends AbstractActionControl
{
use MailServiceAwareTrait;
public function processAction()
{
$this->getMailService()->send($to, $from, $subject, $body);
}
}
"Sending emails" is a service, so typically it should be in a separate model file (aka service file), not in the controller. While you actually can put it in a controller as a function, but that will simply means you are completely misusing the MVC concept itself.
Anyway, I'll answer how to do it but I strongly do NOT recommend it. In your controller (for example, IndexController), this is what you can do:
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController {
public function indexAction() {
// This below line will call FooController's barAction()
$otherViewModel = $this->forward()->dispatch(\Application\Controller\FooController::class, ['action'=>'bar']);
$otherViewModel->setTemplate('application/foo/bar');// you must set which template does this view use
return $otherViewModel;
}
}
Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.
I'm trying to create a manager to handle basic requests of a controller (list, new, edit, delete). I need to inject the form factory within the constructor of this service. By what name should I call?
I need something like this:
lp_ExpedienteManager:
class: AppBundle\Services\ExpedienteManager\ExpedienteManager
arguments: [ "#doctrine.orm.entity_manager", "#security.token_storage", "#form_factory" ]
Thanks for your time!
For future references, since Symfony 3.3 this service is available as Symfony\Component\Form\FormFactoryInterface. So you can inject in your services like
use Symfony\Component\Form\FormFactoryInterface;
class AccountBridge
{
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function accountCreateAction(Account $account)
{
$form = $this->formFactory->create(AccountType::class, $account);
}
}
what is the best practice for injecting repositories, entity managers to a service?
I figured I can do it in at least three ways.
1. Injecting repositories, managers through the constructor
This way the service can be covered with tests quite easily. All you need is to pass mocked dependencies to the constructor and you're ready.
class TestService
{
public function __construct(MyEntityRepository $my, AnotherEntityRepository $another, EntityManager $manager)
{
$this->my = $my;
$this->another = $another;
$this->manager = $manager;
}
public function doSomething()
{
$item = $this->my->find(<...>);
<..>
$this->manager->persist($item);
$this->manager->flush();
}
}
2. Passing just the EntityManager
This is a bit more difficult to test if you need like 4 repositories from the same manager. I figured this way you have to mock manager's getRepository calls.
class TestService
{
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
public function doSomething()
{
$item = $this->manager->getRepository('my')->find(<...>);
<..>
$this->manager->persist($item);
$this->manager->flush();
}
}
3. Passing the whole registry
This way you don't get circular reference exception with doctrine event subscribers, but it's harder to mock everything.
Also this is the only way sensiolabs insights doesn't give me an architectural violation for injecting EntityManager.
class TestService
{
public function __construct(RegistryInterface $registry)
{
$this->doctrine = $registry;
}
public function doSomething()
{
$item = $this->registry->getManager()->getRepository('my')->find(<...>);
<..>
$this->registry->getManager()->persist($item);
$this->registry->getManager()->flush();
}
}
What is the best practice to do this and why?
I always try to inject my services as specific as possible.
Which means I always inject repositories since that is easier when writing tests. Otherwise you have to mock the registry and or manager too.
I know this is old but I just thought I'd add my 2 cents. I follow what Matthias Noback says in these two blog posts:
Inject a repository instead of the EntityManager
Inject the ManagarRegistry instead of the EntityManager
So I inject the specific repository whenever I need to find an entity, but I also inject the ManagerRegistry if I need to call flush, persist or remove, because otherwise you have to put proxy functions for them in all your repositories.