graphaware/neo4j-php-ogm event listeners - symfony

I recently created a new symfony project (3.1) with a dependency on graphaware/neo4j-php-ogm and neo4j/neo4j-bundle to manage my database.
Then I created a new Entity class named User with properties (login, password, ...) and I want to automatically set the current date before the flush event occurs (on preFlush).
I saw the PRE_FLUSH constant in neo4j-php-ogm/src/Events.php (https://github.com/graphaware/neo4j-php-ogm/blob/master/src/Events.php) but I haven't found any information about it in the documentation.
Well, my question is : Can we use this functionality in the actual version of the OGM ? If yes, do you have an example of the usage ?
Thank you for your help !

Yes you can, it is not documented you are right, I'll make sure it will be soon.
Integration test here : https://github.com/graphaware/neo4j-php-ogm/blob/master/tests/Integration/EventListenerIntegrationTest.php
First, You need create a class that will act as EventListener to the preFlush event of the EntityManager and a method reacting to the event :
<?php
namespace GraphAware\Neo4j\OGM\Tests\Integration\Listeners;
use GraphAware\Neo4j\OGM\Event\PreFlushEventArgs;
use GraphAware\Neo4j\OGM\Tests\Integration\Model\User;
class Timestamp
{
public function preFlush(PreFlushEventArgs $eventArgs)
{
$dt = new \DateTime("NOW", new \DateTimeZone("UTC"));
foreach ($eventArgs->getEntityManager()->getUnitOfWork()->getNodesScheduledForCreate() as $entity) {
if ($entity instanceof User) {
$entity->setUpdatedAt($dt);
}
}
}
}
Then you can register this event listener after having creating the entity manager :
/**
* #group pre-flush
*/
public function testPreFlushEvent()
{
$this->clearDb();
$this->em->getEventManager()->addEventListener(Events::PRE_FLUSH, new Timestamp());
$user = new User("ikwattro");
$this->em->persist($user);
$this->em->flush();
$this->assertNotNull($user->getUpdatedAt());
var_dump($user->getUpdatedAt());
}
Result of the test :
ikwattro#graphaware-team ~/d/g/p/ogm> ./vendor/bin/phpunit tests/ --group pre-flush
PHPUnit 5.6.2 by Sebastian Bergmann and contributors.
Runtime: PHP 5.6.27
Configuration: /Users/ikwattro/dev/graphaware/php/ogm/phpunit.xml.dist
. 1 / 1 (100%)int(1486763241)
Time: 378 ms, Memory: 5.00MB
OK (1 test, 1 assertion)
Result in the database :

Thank you a lot ! It's work perfectly. If anyone want to use it don't forget to type your property as "int". ;)

Related

Cannot autowire argument $article : Symfony 5

I'am currenty face to this error when i try to get my article page : Cannot autowire argument $article of "App\Controller\HomeController::show()": it references class "App\Entity\Article" but no such service exists.
I just created a very new small symfony 5 application :
The code in my controller is :
/**
* #Route("/show/{id}", name="show")
*/
public function show(Article $article): Response
{
if(!$article) {
return $this->redirectToRoute('home');
}
return $this->render('show/index.html.twig', [
'article' => $article,
]);
}
I'am following a tutorial on Udemy and i guess that i did the same thing than the former. Maybe version difference ?
Thanks in advance for your helps.
Thierry
The error you have is saying that it cannot find the auto-wire path to your entity. Using the Symfony installer will automatically configure parts like this for you.
The main part which auto-wires the Entity to the Dependency Injection layer is through Doctrine configuration and the Symfony ParamConverter found as part of the SensioFrameworkExtraBundle.
The ParamConverter has a lot of options to be able to convert parameters inside of the #Route into an Entity. Using your example above we see that Symfony will internally call ArticleRepository->findOneById($id) and return the result to your controller.
EDIT: Removed all mentions of Doctrine configurations. The errors that are displayed when Doctrine is not auto-wiring are different.
Same problem here (with attributs instead of annotations) on Symfony 5
I have even tried to explicite ParamConverter and Entity:
#[Route('/article/{id}/editer', name: 'edit_article', requirements: ["id" => "\d+"], methods: ['GET','POST'])]
#[ParamConverter('id', options: ['mapping' => ['id' => 'id']])]
#[Entity('article', expr: 'repository.findOneBySlug(id)')]
public function editArticle(Article $article, Request $request,
CategoryRepository $categoryRepository, EntityManagerInterface $manager): Response
{
dump($article); die;
}
but i have got the error message:
Cannot autowire argument $id of "App\Controller\DefaultController::editArticle()":
it references class "App\Entity\Article" but no such service exists.
Although there is:
use App\Entity\Article;
And the solution like #nico-haase said is to install this bundle
composer require sensio/framework-extra-bundle

Symfony Tactician-bundle Typehints = Missing handler method error

I've used the thephpleague/tactician-bundle with Symfony before, but this is the first time I've used it with Symfony 4.* (specifically 4.1.4) and attempted to use a single handler Class for my Application Service.
When I execute a command in the Controller
public function postAction(Request $request, CommandBus $commandBus)
{
$form = $this->createForm(VenueType::class);
$form->submit($request->request->all(), true);
$data = $form->getData();
if($form->isValid()) {
$command = new CreateVenueCommand($data);
$commandBus->handle($command);
return $form->getData();
}
return $form;
}
... I get the following error:
"error": {
"code": 500,
"message": "Internal Server Error",
"exception": [
{
"message": "Could not invoke handler for command App\\Application\\Command\\CreateVenueCommand for reason: Method 'handle' does not exist on handler",
"class": "League\\Tactician\\Exception\\CanNotInvokeHandlerException",
"trace": [
I've seemingly followed the installation documents for the tactician-bundle and installed it using Flex. As far as I can tell everything is configured correctly, so I'm unsure what I'm missing in my implementation.
Implementation
As per the thephpleague/tactician-bundle installation guide I've installed using Flex and the bundle is registered and the config package installed:
tactician:
commandbus:
default:
middleware:
- tactician.middleware.locking
- tactician.middleware.doctrine
- tactician.middleware.command_handler
After creating the DTO Command Class 'CreateVenueCommand', I created the handler Class:
use App\Infrastructure\Domain\Model\VenueRepositoryInterface;
use App\Application\Command\CreateVenueCommand;
use App\Domain\Entity\Venue;
class VenueApplicationService
{
private $venueRepository;
public function __construct(VenueRepositoryInterface $venueRepository)
{
$this->venueRepository = $venueRepository;
}
/**
* #param CreateVenueCommand $aCommand
* #throws \Exception
*/
public function createVenue(CreateVenueCommand $aCommand)
{
$aVenue = new Venue($aCommand->getData())
if ($aVenue === null) {
throw new \LogicException('Venue not created');
}
$this->venueRepository->add($aVenue);
}
Then I registered the handler Class as a Service taking advantage of Symfony's autowiring and Tacticians typehints:
App\Application\VenueApplicationService:
arguments:
- '#App\Infrastructure\Persistence\Doctrine\DoctrineVenueRepository'
tags:
- { name: tactician.handler, typehints: true }
So according to the installation documents, typehints work if:
The method must be public.
The method must accept only one parameter.
The parameter must be typehinted with a class name.
Also, and this is specific to my use case:
If you have multiple commands going into a single handler, they will all be detected, provided they follow the rules above. The actual name of the method is NOT important.
So when I invoke the commandbus in the Controller Class, I'm unsure why I'm getting the error above.
If I change the Command Handler method to:
public function handle(CreateVenueCommand $aCommand)
{
... then it works fine. This would seem to suggest that the typehints aren't working as documented.
It seems in this case that the actual name of the method IS important. ... or I've made some form of error in my implementation ... or I'm misunderstanding the multiple commands going into a single handler use case??
Any assistance would be greatly appreciated.
Solution
With a big thanks to kunicmarko20 for pointing me in the right direction.
Specifically for my use case I simply needed to use one of Tacticians MethodNameInflector classes, configured in Symfony thus:
tactician:
commandbus:
default:
middleware:
- tactician.middleware.locking
- tactician.middleware.doctrine
- tactician.middleware.command_handler
method_inflector: tactician.handler.method_name_inflector.handle_class_name
... then it was simply a matter of naming each Handler method in my Application Service class 'handle{whateverYouLike}Command
Here under 1. is explained how the naming works, if you want to use a different name than in this table you can implement MethodNameInflector Interface and provide a name of the method.

symfony 3.1 Check if a bundle is installed

I'm developing a bundle who has a dependency on another one.
In order to handle the case that the base bundle has not been installed I'll like to perform a "bundle_exists()" function inside a controller.
The question is: How can I have a list of installed bundles or How can I check for the name (eventually also the version) of a bundle.
Thanks.
In addition to #Rooneyl's answer:
The best place to do such a check is inside your DI extension (e.g. AcmeDemoExtension). This is executed once the container is build and dumped to cache. There is no need to check such thing on each request (the container doesn't change while it's cached anyway), it'll only slow down your cache.
// ...
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$bundles = $container->getParameter('bundles');
if (!isset($bundles['YourDependentBundle'])) {
throw new \InvalidArgumentException(
'The bundle ... needs to be registered in order to use AcmeDemoBundle.'
);
}
}
}
Your class needs to have access to the container object (either by extending or DI).
Then you can do;
$this->container->getParameter('kernel.bundles');
This will give you a list of bundles installed.
Update;
If you are in a controller that extends the Symfony\Bundle\FrameworkBundle\Controller\Controller or in a command class that extends Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand, you can just get the parameter.
$this->getParameter('kernel.bundles').
Else #Wouter J's answer is your best answer.
You can get a list of all Bundles from the Kernel like this:
public function indexAction ()
{
$arrBundles = $this->get("kernel")->getBundles();
if (!array_key_exists("MyBundle", $arrBundles))
{
// bundle not found
}
}
From Andrey at this question: How do I get a list of bundles in symfony2?
If you want to call a non static method of registered bundle object (not class) then you can do the following:
$kernel = $this->container->get('kernel');
$bundles = $kernel->getBundles();
$bundles['YourBundleName']->someMethod();
Where 'YourBundleName' is the name of your bundle, which you can get by calling from console:
php app/console config:dump-reference

Override a symfony service tag with a compiler pass

I'm trying to override a tag in a symfony service definition with a compiler pass. The service as an example would be data_collector.translation.
The goal is to deactivate the data collector service to disable the element in the symfony web developer toolbar. To do this, I have to set the priority of the data_collector tag to 0.
I could also override it in my own service definition:
services:
data_collector.translation:
class: 'Symfony\Component\Translation\DataCollector\TranslationDataCollector'
tags:
- {name: 'data_collector', priority: '0'}
arguments: [#translator.data_collector]
But as I want to do this for a few of the data collectors, I would need to know the mandatory arguments for the data collector definition. The priority works the same for all collectors and therefore I would only need the name of the collector to disable it.
So I wrote the following compiler pass:
class DataCollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('data_collector.translation')) {
return;
}
$definition = $container->getDefinition('data_collector.translation');
$tags = $definition->getTags();
$tags['data_collector'][0]['priority'] = 0;
$definition->setTags($tags);
$container->setDefinition('data_collector.translation', $definition);
}
}
To make things more wired: When I run this command:
$ php app/console container:debug --show-private --tag='data_collector'
I get the following output:
data_collector.translation #WebProfiler/Collector/translation.html.twig translation 0 Symfony\Component\Translation\DataCollector\TranslationDataCollector
So the priority even in the debugger is set to 0.
But for which reason ever the element is still shown in the toolbar.
What did I do wrong here? Is there another mechanism for overwriting a tag within a compiler pass?
The compiler pass does run (tested it with printing out stuff)
I'm using Symfony 2.7.1
Turns out the code does work, the only problem is, that the CompilerPass is run after the ProfilerPass which is part of the FrameworkBundle. Putting my bundle with the CompilerPass before the FrameworkBundle in the AppKernel solves the problem (more information here). For not even initiating the data collectors it's better to remove all tags instead of just setting the priority to 0.
That's what the final solution looks like:
class DataCollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$collectorsToRemove = [
'data_collector.form',
'data_collector.translation',
'data_collector.logger',
'data_collector.ajax',
'data_collector.twig'
];
foreach($collectorsToRemove as $dataCollector) {
if (!$container->hasDefinition($dataCollector)) {
continue;
}
$definition = $container->getDefinition($dataCollector);
$definition->clearTags();
}
}
}
Can you try this?
if (!$container->hasDefinition('data_collector.form')) {
return;
}
$definition = $container->getDefinition('data_collector.form');
$definition->clearTags();
$container->setDefinition('data_collector.form', $definition);
Why not use your compiler pass to manipulate directly the service Definition of the service holding all these collectors ?
If I look at the compiler pass responsible for loading the data collector, it seems that they are all injected using a method call injection.
You could use your compiler pass to rewrite the method call array using methods like setMethodCalls, removeMethodCall, ... of the Definition entity.
The method call manipulation documentation : link

Symfony2 custom console command not working

I created a new Class in src/MaintenanceBundle/Command, named it GreetCommand.php and put the following code in it:
<?php
namespace SK2\MaintenanceBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('maintenance:greet')
->setDescription('Greet someone')
->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?')
->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
if ($name) {
$text = 'Hello '.$name;
} else {
$text = 'Hello';
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
}
$output->writeln($text);
}
}
?>
And tried to call it via
app/console maintenance:greet Fabien
But i always get the following error:
[InvalidArgumentException]
There are no commands defined in the "maintenance" namespace.
Any ideas?
I had this problem, and it was because the name of my PHP class and file didn't end with Command.
Symfony will automatically register commands which end with Command and are in the Command directory of a bundle. If you'd like to manually register your command, this cookbook entry may help: http://symfony.com/doc/current/cookbook/console/commands_as_services.html
I had a similar problem and figured out another possible solution:
If you override the default __construct method the Command will not be auto-registered by Symfony, so you have to either take the service approach as mentioned earlier or remove the __construct override and make that init step in the execute method or in the configure method.
Does actually anyone know a good best practice how to do init "stuff" in Symfony commands?
It took me a moment to figure this out.
I figured out why it was not working: I simply forgot to register the Bundle in the AppKernel.php. However, the other proposed answers are relevant and might be helpful to resolve other situations!
By convention: the commands files need to reside in a bundle's command directory and have a name ending with Command.
in AppKernel.php
public function registerBundles()
{
$bundles = [
...
new MaintenanceBundle\MaintenanceBundle(),
];
return $bundles;
}
In addition to MonocroM's answer, I had the same issue with my command and was silently ignored by Symfony only because my command's constructor had 1 required argument.
I just removed it and call the parent __construct() method (Symfony 2.7) and it worked well ;)
If you are over-riding the command constructor and are using lazy-loading/autowiring, then your commands will not be automatically registered. To fix this you can add a $defaultName variable:
class SunshineCommand extends Command
{
protected static $defaultName = 'app:sunshine';
// ...
}
Link to the Symfony docs.
I think you have to call parent::configure() in your configure method
I had this same error when I tried to test my command execution with PHPUnit.
This was due to a wrong class import :
use Symfony\Component\Console\Application;
should be
use Symfony\Bundle\FrameworkBundle\Console\Application;
cf. Other stack thread
In my case it was complaining about the "workflow" namespace although the WorkflowDumpCommand was correctly provided by the framework.
However, it was not available to run because I have not defined any workflows so the isEnabled() method of the command returned false.
I tried to use a service passed via constructor inside the configure method:
class SomeCommand extends Command {
private $service;
public function __construct(SomeService $service) {
$this->service = $service;
}
protected function configure(): void {
$this->service->doSomething(); // DOES NOT WORK
}
}
Symfony uses Autoconfiguration that automatically inject dependencies into your services and register your services as Command, event,....
So first just make sure that you have services.yaml in your config folder. with autoconfigure:true.
this is the default setting
Then Make sure That All your files are exactly the same name as Your Class.
so if you have SimpleClass your file must be SimpleClass.php
If you have a problem because of a __constructor,
go to services.yml and add something like this:
app.email_handler_command:
class: AppBundle\Command\EmailHandlerCommand
arguments:
- '#doctrine.orm.entity_manager'
- '#app.email_handler_service'
tags:
- { name: console.command }
For newer Symfony-Version (5+) commands must be registered as services.
What I do frequently forget while setting it up, is to tag it properly:
<service id="someServiceCommand">
<tag name="console.command"/>
</service>
Without this litte adaptation, your command name will not be displayed and therefore not accessible.

Resources