DependencyInjection for EventSubscribers in Symfony 2.7 - symfony

I am doing some file operations in S3 and need to perform a few actions after a temporary file has been successfully copied following an API request. For example, update the filesize stored in the DB.
I'd like to dispatch an event in the cases where this action needs to occur but any subscriber is going to need a few services like the filesystem and an entity manager and I cannot figure out how to inject services into the EventSubscriber since it needs to be created and added to the EventDispatcher as a subscriber in a class that is not aware of the filesystem or doctrine, or the container.
I've attempted to use the ContainerAwareEventDispatcher. Here's my event being dispatched in that way:
class PendingFile implements SourceFile
{
/**
* #var string
*/
private $filename;
/**
* #var PendingFileService
*/
private $pendingFileService;
/**
* #param string $filename
* #param PendingFileService $pendingFileService
*/
public function __construct($filename, PendingFileService $pendingFileService)
{
$this->filename = $filename;
$this->pendingFileService = $pendingFileService;
}
/**
* #param string $targetFilename
* #param Media $media
*/
public function process($targetFilename, Media $media)
{
$this->pendingFileService->copyFile($this->filename, $targetFilename);
$event = new PendingFileCopyEvent($media);
$eventDispatcher = new ContainerAwareEventDispatcher(new ContainerBuilder());
$eventDispatcher->addSubscriberService(
'acme.media.event_subscriber.pending_file_copy',
'Acme\MediaBundle\EventSubscriber\PendingFileCopySubscriber'
);
$eventDispatcher->dispatch(PendingFileCopyEvent::EVENT_NAME, $event);
}
}
Unfortunately, this container is empty and does not recognise my service. I can't inject these services into the entity itself. And if I could, why would I use an event subscriber anyway...
Question: How can I properly build my subscriber as a service with the dependencies it needs?

In general you should only use one instance of an event dispatcher. Symfony has a #event_dispatcher service. You should use it instead of instantiate a new one. So in your PendingFile Class, add a EventDispatcherInterface $eventDispatcher in your constructor, then just dispatch an event like you do.
For the subscriber, you need to create the class, then declare it as a service with a tag kernel.event_subscriber like explained in the doc to automatically register the event subscriber in the event dispatcher (so you can removed the addSubscriberService line of your code).
And Voila!

Related

How to properly use postUpdate, postRemove, postPersist in Doctrine?

The documentation says: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/events.html#postupdate-postremove-postpersist
The three post events are called inside EntityManager#flush(). Changes in here are not relevant to the persistence in the database, but you can use these events to alter non-persistable items, like non-mapped fields, logging or even associated classes that are not directly mapped by Doctrine.
So let's imagine I have an Image entity:
<?php
/**
* #ORM\Entity
*/
class Image
{
/**
* #var int|null
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #var string
* #ORM\Column(type="string")
*/
private string $path;
/**
* #var string
* #ORM\Column(type="string")
*/
private string $status = 'RECEIVED';
}
Once I flush my Image entity I want to upload the corresponding file on a FTP server, so I do it in the postPersist event.
But if the upload on FTP fail I want to change the status of my Image to FTP_ERROR.
<?php
public function postPersist(Image $image, LifecycleEventArgs $event)
{
$em = $event->getEntityManager();
try {
$this->someService->uploadToFtp($image);
} catch (Exception $e) {
$image->setStatus('FTP_ERROR');
$em->persist($image);
$em->flush();
}
}
And it works, but as documentation says it's not a good way to do it.
I have seen this post: Flushing in postPersist possible or not? which says:
#iiirxs:
It is ok to call flush() on a PostPersist lifecycle callback in order to change a mapped property of your entity.
So how to do so? #iiirxs and the documentation say 2 different things.
The best way to download something on such event as postPersist is using https://symfony.com/doc/current/components/messenger.html
so, you can create asynchronous task for file uploading and Symfony Messenger will be able to handle errors and retry it automatically on fail, also you will be able to update it, status and etc in separate process and it will not depend on specific doctrine cases like case in your question.

How to Inject Services in another Service using Factory to create them

I have a little problem with the creation of my services in symfony 4.1
I use Factories to create my services, and to force the Factory to have the expected method I made an Interface
<?php
namespace App\Service\Factory\Interfaces;
use App\Service\Interfaces\BaseModelServiceInterface;
use Doctrine\ODM\MongoDB\DocumentManager;
/**
* Interface ModelServiceFactoryInterfaces
* #package App\Service\Factory\Interfaces
*/
interface ModelServiceFactoryInterfaces
{
/**
* Create the Model related Service
*
* #return BaseModelServiceInterface
*/
public function createService(DocumentManager $dm);
}
I get the DocumentManager from autowired services to create the Repository in the Factory and pass it to the service, like this
/**
* Class ChapterServiceFactory
* #package App\Service\Factory
*/
class ChapterServiceFactory implements ModelServiceFactoryInterfaces
{
/**
* #param DocumentManager $dm
* #return ChapterService|BaseModelServiceInterface
*/
public function createService(DocumentManager $dm)
{
$chapterRepository = $dm->getRepository(Chapter::class);
/**
* #var $chapterRepository ChapterRepository
*/
return new ChapterService($chapterRepository);
}
}
The problem with that is, if I want to have another service in my ChapterService I can't autowire it in the Factory because of the Interface, but I don't want to delete the Interface either.
Is there a way to have "dynamic arguments" with an Interface, or another way than the Interface to force the Factories to have the createService method ?
There's no workaround for this: if you declare an interface as the argument, then you need to provide an explicit implementation for that interface. This means that you cannot declare two default implementations for the same interface. Only thing you can do in this case is to declare explicitly the service and all its arguments.
By the way, as you're writing about Doctrine Repositories, I suggest you to take a look at ServiceEntityRepository: extending this class from one of your repos will make automatically the report a service that you can inject where needed.

Multiple files for Doctrine Fixtures for Symfony 3

I'm using the bundle Doctrine:Fixtures to load an example of bbdd throw the Entitys and since I work alone in the project it's ok.
But now, I got a colleague in my project and we were wondering if it's possible to set up a different files for the loading.
I got my own file of fixtures associated in git and I don't want him modifying this file. I would like to have an special file just for him that will allows him to modify whenever he wants this file of fixtures. So, anyone can have his owns records in the init of the bbdd.
If it's not possible with multiple files, could be possible in another way?
http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
You can load specific fixture files using the --fixture flag:
php app/console doctrine:fixtures:load --fixture=/src/BundleName/DataFixtures/ORM/Fixture.php
Or, you could add a Fixtures.php.dist file with some working examples, then use .gitignore to ignore Fixtures.php.
Then add a command into your build (or in composer scripts), and/or your documentation to copy this .dist file to Fixtures.php when checking out the project.
Another method if you're doing BDD is to create a Helper class that can be used in your Context to create and persist entities as you need them in tests. This would allow you to create only specifics needed for the test. All it really needs is the EntityManager so it may be simpler than pre-defining all the fixtures up front.
You can use Faker to generate realistic entities.
class AbstractFixtureHelper implements ContainerAwareInterface
{
/**
* #var Generator
*/
protected $faker;
/**
* #var ContainerInterface
*/
protected $container;
public function __construct()
{
$this->faker = Factory::create();
}
/**
* #param ContainerInterface|null $container
* #return void
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* #return EntityManager
*/
protected function getEntityManager()
{
return $this->container->get('doctrine.orm.entity_manager');
}
}
Then for different entities - in this example, a user:
class UserFixtureHelper extends AbstractFixtureHelper
{
public function createUser()
{
$user = new User();
$user->setEmail($this->faker->email);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
return $user;
}
}
Then in your Context, inject the UserFixtureHelper and create directly in the scenario steps.
/**
* #Given there is a User who XXX
*/
public function thereIsAUser()
{
$user = $this->userFixtureHelper->createUser();
}

How to get #request_stack service in app/console context?

I have services that require the #request_stack to fetch parameters.
Now, I want to expose certain functionality to console commands callable via ./app/console//. Yet in the context of an ./app/console, there is no #request_stack, yet one can input arguments.
In order to resolve this issue, I am now creating basically two services, one basic, only waiting for the params, and one being able to use the #request_stack.
Yet I dislike that there are two ways for the data to be fetched in the request-based flow and via the app/console.
Hence I am wondering, as I am simply want the data that comes per default via the request to also be able to be inputted via console arguments:
Can I setup a custom request_stack to simulate a request during a console command?
When I was investigating this issue, I stumbled across request stack push method, where a warning was already in place in the doc block:
/**
* Pushes a Request on the stack.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*/
public function push(Request $request)
{
$this->requests[] = $request;
}
So while it would be possible to do it this way, I decided against the approach of my original question and to refactor my application instead.
I have created a context value object which just holds the parameter data:
/**
* Context
**/
class Context
{
/**
* #var string
*/
private $countryCode;
/**
* Context constructor.
* #param string $countryCode
*/
public function __construct($countryCode = '')
{
$this->countryCode = $countryCode;
}
/**
* #return string
*/
public function getCountryCode()
{
return $this->countryCode;
}
}
And a ContextFactory that creates the context with by the request stack:
class ContextFactory extends RequestAwareService
{
/**
* ContextFactory constructor.
* #param RequestStack $stack
*/
public function __construct(RequestStack $stack)
{
$this->setRequestStack($stack);
}
/**
* #return Context
*/
public function create()
{
return new Context($this->request->getCountryCode());
}
}
(The RequestAwareService is just a helper class to more easily parse the request.)
I then defined the services in my Bundle services.yml:
context.factory:
class: Kopernikuis\MyBundle\Service\Config\ContextFactory
arguments:
- '#request_stack'
context:
class: Kopernikuis\MyBundle\Service\Config\Context
factory:
- '#context.factory'
- create
Instead of injecting the #request_stack, I am now injecting my #context value object, which also had the benefit of reducing the hierarchy as now only one service parses the request_stack once, and I also noticed that certain functionality got much simpler as I could remove parameters from method calls, as they were all provided by the context object instead.
And in my custom commands, I can just replace my context
protected function execute(InputInterface $input, OutputInterface $output)
{
// #todo: use variable from InputInterface
$context = new Context('fnordfoo');
$this->getContainer()->set('context', $context);
}
With the newly gained knowledge, I strongly disagree with my original intent of trying to manually set the #request_stack.
Refactoring the code base to not necessarily require the #request_stack was a more solid choice.

How to call doctrine in an entity class using symfony

Am using symfony framework for my application. And to save records in database I want call the $this->getDoctrine()->getManager(); method in my entity class. But when I did that it gave me the error:
Call to undefined method getDoctrine(),
Can some one tell me what is the right way to do this.
My entity class is like:
namespace Acme\SuperbAppBundle\Entity;
use Symfony\Component\DependencyInjection\Container;
use Doctrine\ORM\Mapping as ORM;
class Users
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
* #return Users
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
function __construct($firstName){
$this->setFirstName($firstName);
}
function save(){
$em = $this->getDoctrine()->getManager();
$em->persist($create);
$em->flush();
}
}
And my controller method is like:
public function test(){
$create = new Users('Rajat');
$create->save();
}
Your save method is attempting to call
$this->getDoctrine();
Whereby $this is the current Class, and any other Class it inherits. As it stands, your current Class, User, is standalone, and does not have a getDoctrine() method. If your Class were to extend the Controller Class, it would have access to that method:
class User extends Controller
I believe this simple fix will work, although it probably doesn't make real sense for it to extend Controller, as it is a User Entity, and unrelated to a Controller. A preferred, more advanced method, would be to inject the Doctrine service into the User class.
Ok, first of all Doctrine Entities :
Handle the entity generation and configuration
Declare the operations on the setters and getters.
If you wana save an object into your entity there it's your User, you have two way to store this user:
One:
You can use entity manager to store a user and the entity will help you to create the right object using the seters and getters:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use PATH\TO\Users;
class ExampleController extends Controller
{
public function examplefunction()
{
$em = $this->getDoctrine()->getManager();
$entity = new Users();
$entity->setFirstName('Rajat');
$em->persist($entity);
$em->flush();
}
}
The other way is to create this entry using QueryBuilder but it's a bad way in your case.
Oh, i forgot please delete the save method in your entity Doctrine manager allready implement it.
Your controller probably doesnt extends Symfony\Bundle\FrameworkBundle\Controller\Controller ...
You should have controller defined like this example:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
}
Entity class does not extends ContainerAware / Controller, so you can't call $this->getDoctrine()->getManager(). I don't think your Entity class should extend to a Controller. Because your entity class will become a controller instance just because you want to access the doctrine manager. That's a not good practice. What you can do is inject doctrine manager to your Entity class through services.
I wrote a blog few weeks ago regarding injecting services container and accessing through constructor. You can inject doctrine entity manager in the same way you inject services container. You can take a look at that if you like :- http://anjanasilva.com/blog/injecting-services-in-symfony-2/
Here's a nice question regarding injecting doctrine manager. Make sure you read the answer as well. :- Symfony 2 EntityManager injection in service
And another nice tutorial on injecting custom repository manager instead of injecting the whole entity manager. Which I believe even a good solution. :- http://php-and-symfony.matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/
Hope this helps to increase your understanding about Symfony 2.
Cheers!

Resources