I have a test class that reports an undefined variable and I cannot seem to understand what the issue is.
Basically the listener below is suppose to listen to an application boot event documented in the class below:
<?php
namespace Colleen\Core\Event\Application;
final class ApplicationBootedEvents
{
const APP_BOOTED = 'application.booted';
}
My event class is as shown below which receives an instance of the application itself.
<?php
namespace Colleen\Core\Event\Application;
use Symfony\Component\EventDispatcher\Event;
use Colleen\Core\Application;
/**
* The application.booted event is dispatched each time
* an application instance is created in the system.
*
*/
class ApplicationBootedEvent extends Event
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getApplication()
{
return $app;
}
}
These two classes to me look perfect according to Symfony's documentation on the Event Dispatcher Component. Following is the listener class that is suppose to listen to ApplicationBootedEvents::APP_BOOTED event.
<?php
namespace Colleen\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
class ApplicationBootedListener
{
public function onBoot(ApplicationBootedEvent $event)
{
$container = $event->getApplication()->getContainer();
$container->set('class.dispatcher', '\\Symfony\\Component\\EventDispatcher\\EventDispatcher');
}
}
The Listener class does nothing at the moment and my test case is to test whether the "class.dispatcher" key exist on my container which simple extends Pimple and is made available through the Application Object.
Below is my test that shows how these will eventually be used in my front controller or any class that stands between them and the front controller.
<?php
namespace Colleen\Qa\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\Listener\ApplicationBootedListener;
use Colleen\Core\Event\Application\ApplicationBootedEvents;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Colleen\Core\Container\Container;
use Colleen\Core\Application;
class AppliocationBootedListenerTest extends \PHPUnit_Framework_TestCase
{
public function testApplicationBootListener()
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(
ApplicationBootedEvents::APP_BOOTED, array(
new ApplicationBootedListener(), 'onBoot'
));
$app = $dispatcher->dispatch(ApplicationBootedEvents::APP_BOOTED, new ApplicationBootedEvent(new Application(new Container())))->getApplication();
$expected = '\\Symfony\\Component\\EventDispatcher\\EventDispatcher';
$actual = $app->getContainer()->get('class.dispatcher');
$this->assertSame($expected, $actual);
}
}
The idea is to test whether the Listener gets called and if it is able to feed our application object's container with all the necesary objects we will need to get our web framework to work.
Below is the output I get as a result if running this test case.
There's an error in your ApplicationBootedEvent.php file, on line 24 as the stack trace suggested..
Change
public function getApplication()
{
return $app;
}
To
public function getApplication()
{
return $this->app;
}
Related
I have an EventSubscriberInterface called PostCreation listening to the POST_WRITE event.
The method called when the event occurs must call a service according to the "type" attribute of the object which the event occured on.
The following code is a working example of what I want:
<?php
class PostCreation implements EventSubscriberInterface
{
private $processAfterPostCreationTypeAService;
private $processAfterPostCreationTypeBService;
public function __construct(
ProcessAfterPostCreationTypeAService $processAfterPostCreationTypeAService,
ProcessAfterPostCreationTypeBService $processAfterPostCreationTypeBService
) {
$this->processAfterPostCreationTypeAService = $processAfterPostCreationTypeAService;
$this->processAfterPostCreationTypeBService = $processAfterPostCreationTypeBService;
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::VIEW => ['processPostCreation', EventPriorities::POST_WRITE]];
}
public function processPostCreation(GetResponseForControllerResultEvent $event): void
{
$myObject = $event->getControllerResult();
if (!$event->getRequest()->isMethod('POST') ||
!$myObject instanceof MyClass
) {
return;
}
/* some code */
if($myObject->getType() === 'A') {
$this->processAfterPostCreationTypeAService->doSomething($myObject);
} else if($myObject->getType() === 'B') {
$this->processAfterPostCreationTypeBService->doSomething($myObject);
}
/* some code */
}
}
Doing it that way is not maintainable so I would like to find another solution, but I can't find it myself so I need help:
The constructor of PostCreation can't know the object.type because it's called before the event occurs
I can't instantiate easily ProcessAfterPostCreationTypeAService or ProcessAfterPostCreationTypeBService in processPostCreation method because they are services as well (and needs dependancy injection, configured to be autowired)
I'm sure dependancy injection could help me, but I can't find how.
So how to do something maintainable?
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.
I have an entity BlogPost with a status property. This status property depends on an external API call which is handled via the doctrine postLoad event. All other properties are stored in the local database.
public function postLoad(BlogPost $post)
{
$this->postHandler->calculateStatus($post);
}
The problem is, in some cases i don't want to calculate the status at all. For example if i want to get only the description of all blogposts.
With the code above, all blog entities being loaded will trigger the postLoad event even if i just want to have values from a local database. That is very expensive and not acceptable.
So for example in my repository class i want to get all BlogPosts having a website without invoking the postLoad event.
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getResult();
}
Is there a way to say "Yes, load the BlogPost collection, but do not fire event!" ???
Any other approaches? Custom event?
Thanks
Why don't just move this logic outside the post entity and event listener? If you know when you need to calculate the status you can do it explicitly.
For example
$post = $this->entityManager->find(BlogPost::class, $postId);
$status = $this->postHandler->calculateStatus($post);
The other approach I could suggest is not good but works. You could use lazy calculation and instead of calling $this->postHandler->calculateStatus($this) in postLoad event listener you could inject postHandler service into entity and perform the calculation in the moment you actually need it.
For example if you need calculation when calling $blogPost->getStatus() method, you could do it this way:
interface PostHandlerAwareInterface
{
public function setPostHandler(PostHandlerInterface $postHandler): void;
}
class EntityServiceInjectorEventSubscriber implements EventSubscriber
{
/** #var PostHandlerInterface */
private $postHandler;
public function postLoad($entity): void
{
$this->injectServices($entity);
}
public function postPersist($entity): void
{
$this->injectServices($entity);
}
private function injectServices($entity): void
{
if ($entity instanceof PostHandlerAwareInterface) {
$entity->setPostHandler($this->postHandler);
}
}
}
class BlogPost extends PostHandlerAwareInterface
{
/** #var PostHandlerInterface */
private $postHandler;
private $status;
public function setPostHandler(PostHandlerInterface $postHandler): void
{
$this->postHandler = $postHandler;
}
public function getStatus()
{
if (null === $this->status) {
$this->postHandler->calculateStatus($this);
}
return $this->status;
}
}
If you don't like this idea you still could manage it via (BUT I STRONGLY DO NOT RECOMMEND DO THIS DIRTY HACK) setting the flag to your entity event listener.
You could inject your entity event listener to the code and set flag before fetching data:
class BlogPostCalculateStatusListener
{
/** #var bool */
private $calculationEnabled = true;
public function suspendCalculation(): void
{
$this->calculationEnabled = false;
}
public function resumeCalculation(): void
{
$this->calculationEnabled = true;
}
public function postLoad(BlogPost $post): void
{
if ($this->calculationEnabled) {
$this->postHandler->calculateStatus($post);
}
}
}
$this->calculateStatusListener->suspendCalculation();
$blogPosts = $blogPostRepository->findBlogPosts();
$this->calculateStatusListener->resumeCalculation();
Hope this helps.
PS. If you want to get only the descriptions of all blog posts you can do this way:
class BlogPostRepository
{
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp.description')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getArrayResult();
}
}
getArrayResult does not invoke lifecycle callbacks.
Since i haven't found a real similar use case on the internet, i'll go for the following solution which seems the easiest and most acceptable cleanest to me. Maybe someone else could find this useful.
Implement a TransientLoadable Interface
interface TransientLoadable
{
public function isLoaded() : bool;
public function setLoaded(bool $loaded) : TransientLoadable;
public function setTransientLoadingFunction(\Closure $loadingFunction) :
TransientLoadable;
}
Implement the entity
class BlogPost implements TransientLoadable
{
...
}
Setup Loading function on postLoad Event
public function postLoad(BlogPost $post)
{
$func = function() use ($postHandler, $post)
{
//Since there may be another fields being loaded from the same API, catch them also since data is anyway in the same request
$postHandler->setAllDataFromAPI($post)
//Set the loading state to true to prevent calling the API again for the next property which may also be transient
$post->setLoaded(true);
}
$post->setTransientLoadingFunction($func)
}
Use the built-in lazy loading mechanism to get the property from the API only when it's needed
class BlogPost implements TransientLoadable
{
private function getStatus() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->status;
}
private function getVisitorCount() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->visitorCount;
}
}
So what's happening? Let's imagine we want to get the status and the visitor count, both are loaded via a single external API call.
If some api-dependent property of the entity is needed, all other properties gets loaded too (since we don't want to have for each property another call). This in ensured through the loaded function of the TransientLoadable interface. All data gets loaded by the setAllDataFromAPI function which is injected as a closure function.
I think that is not the cleanest solution. The loading stuf should be done by an extra layer on top of the entity class. Since sonata admin does not deal with such an layer, i think that this solution is cleaner than writing the loading mechanism directly to the entity class.
I am open to another suggestions or feedback
Thanks
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.