class EtudiantController extends AbstractController
{
private $etudiant ;
private $form ;
public function __construct()
{
$this->etudiant = new Etudiant();
$this->form = $this->createForm(EtudiantType::class, new Etudiant());
}
}
** i'v got an error when instantiate a form in a constructor using the createForm() function **
Here is the wrong way to solve your problem:
class EtudiantController extends AbstractController
{
private $form;
public function __construct(FormFactoryInterface $formFactory)
{
$this->form = $formFactory->create(TextType::class, new Etudiant());
}
}
I say it is wrong (even though it will work) because creating things like forms really should be done in individual controller actions, not hidden in the constructor. You might be trying to apply Dont Repeat Yourself (DRY) but in cases like this, Don't Confuse Your Future Self takes precedence.
And as far as why injecting the form factory is necessary, I would once again urge you to look at the Symfony source code for AbstractController as well as ControllerTrait. Understanding how dependency injection works is critical to being able to effectively use the framework.
Related
Symfony's SessionInterface is still a little bit vague / magic for me. So if someone can enlighten me, please.
Naturally, the request contains a session object and can be used within a controller.
class SimpleController extends AbstractController
{
public function index(Request $request): Response
{
$request->getSession()->set('session-var', 10);
}
}
However, within a service you can also include the SessionInterface as a service and work with, presumably, the same ParameterBag.
class SimpleService
{
public function __construct(SessionInterface $session)
{
$session->set('session-var', 10);
}
}
My colleague pointed out that a session is (and should always be) part of the request. This makes sense to me, but also got me thinking: why are you able to us the SessionInterface as a service when it is a object/property of the request.
What I want to achieve in the end is to include a service in my controller and in this service work with the current session.
Code example of my use case would look something like this.
class SimpleController extends AbstractController
{
private $simpleService;
public function __construct(SimpleService $simpleService)
{
$this->simpleService = $simpleService;
}
public function index(Request $request): Response
{
$this->simpleService->doSomething();
}
}
class SimpleService
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function doSomething()
{
// Do some things..... and save the result in the session.
$this->session->set('just_a_preference', 50);
}
}
So:
Is this the/a correct way to work with sessions in Symfony?
Is 'just_a_preference' saved in the same session parameter bag as my $request->getSession()?
If not, what is the correct way to work with the current request session outside the controller?
If work with Symfony 4.4.
Both methods are fine. They both reference the same object. Accessing the session via the interface is preferred though. I can come up with at least three reasons why:
extend the functionality: the service behind the interface can be easily decorated – eg. LoggingSessionDecorator which would log session activity
proper OOP code: with $request->getSession()->set(…) you violate the Law of Demeter principle, that said:
it's easier to mock the session in your unit tests
In the end – yes, it doesn't matter. Both methods reference the same object
As said in 1, better to use the interface. At least in my opinion.
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.
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.
is there any way to preprocess controller data somehow. I'm going to take param from session, validate it and assign it as controller property and use it as $this->myVar inside actions of some controller or all of them if possible. Using controller's constructor gives me nothing, I couldn't access request and session data. Thanks!
UPD:
Thanks, jkucharovic, very good solution.
Also there is a bit dirtier solution, without injecting: setContainer() method, which has been called straight after $controller = new Controller();
use Symfony\Component\DependencyInjection\ContainerAwareInterface,
Symfony\Component\DependencyInjection\ContainerInterface;
class AppServiceController extends Controller {
private $my_property;
/**
* Used as constructor
*/
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->my_property = 'foo';
// your controller code
}
}
I'm not sure what you wan't to do is very usefull. A Controller instance will be created each time the controller is called. The session and request will be different each time you call the controller.
I think you should create a BaseController class extending Controller class with a shortcut method to access your MyVar value in session.
class BaseController extends Controller
{
public function getMyVar()
{
return $this->get('session')->get('MyVarSessionKey');
}
}
All your other Controller will extend from this BaseController.
To get the request, just use the shortcut method provided by Controller class, Controller::getRequest().
If you want to use services in __construct method, you have to inject that services first. Then you can use them before any other methods. For example:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session;
public function __construct(Request $request, Session $session)
{
…
}