Symfony 4 autowiring not working properly - symfony

Here is the context:
Installed beanstalk bundle with composer "composer require leezy/pheanstalk-bundle"
I'm trying to using inside a command but i get this error
Cannot autowire service "App\Command\Worker\ProcessParserCommand": argument "$pheanstalk" of method "__construct()" references interface "Pheanstalk\Contract\PheanstalkInterface" but no such
service exists. You should maybe alias this interface to the existing "leezy.pheanstalk.proxy.default" service.
class ProcessParserCommand extends Command
{
protected static $defaultName = 'app:worker:process-parser';
/** #var PheanstalkInterface $pheanstalk */
private $pheanstalk;
protected function configure()
{
$this
->setDescription("Parse something")
;
}
public function __construct(PheanstalkInterface $pheanstalk)
{
$this->pheanstalk=$pheanstalk;
parent::__construct();
}
}

Turns out that this was one of those deceptive error messages.
Normally when you get an "Interface does not exist, maybe alias SomeService" message it means that the Interface needs to be explicitly defined as an alias:
# config/services.yaml
Pheanstalk\Contract\PheanstalkInterface:
alias: 'leezy.pheanstalk.proxy.default'
But in this case, while doing so gets you past the Interface error, a new "too few constructor arguments" error is produced.
A peek at the bundle's documentation shows that you need a bit of configuration to actually generate a pheanstalk instance. The composer require command is smart enough to add the bundle to your bundles.php file but does not create a config file. So add the config file per the docs:
# config/packages/leezy_pheanstalk.yaml
leezy_pheanstalk:
pheanstalks:
primary:
server: beanstalkd.domain.tld
default: true
And presto. The error goes away. As a bonus, the alias in config/services.yaml is no longer needed and should be removed if you added it.

Related

ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface service does not exists

Hey Im trying API Platform with Symfony 6.0 (and PHP 8)
Everything was going alright until I needed to make a DataPersister so I can encrypt the user password before saving it
I literally copied the example in the docs (here https://api-platform.com/docs/core/data-persisters/#decorating-the-built-in-data-persisters) since my entity is actually called User:
<?php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\User;
final class UserDataPersister implements ContextAwareDataPersisterInterface
{
private $decorated;
public function __construct(ContextAwareDataPersisterInterface $decorated)
{
$this->decorated = $decorated;
}
public function supports($data, array $context = []): bool
{
return $this->decorated->supports($data, $context);
}
public function persist($data, array $context = [])
{
$result = $this->decorated->persist($data, $context);
return $result;
}
public function remove($data, array $context = [])
{
return $this->decorated->remove($data, $context);
}
}
I just removed the mailer parts cause what Im trying to do has nothing to do with that. Other than that, it is exactly equal to the example
But it wont work. I get this error when I try to persist:
Cannot autowire service "App\DataPersister\UserDataPersister": argument "$decorated" of method "__construct()" references interface "ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface" but no such service exists. Try changing the type-hint to "ApiPlatform\Core\DataPersister\DataPersisterInterface" instead.
I tried doing what the error suggests but it seems to throw the framework in some endless loop or something cause I get a memory error. And in any case, I need a ContextAwareDataPersisterInterface
Am I doing something wrong or missing something here? Or this a bug? The docs says:
"If service autowiring and autoconfiguration are enabled (they are by default), you are done!"
They are both enabled in services.yaml:
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
I works if I explicity define the service in services.yaml:
App\DataPersister\UserDataPersister:
bind:
$decorated: '#api_platform.doctrine.orm.data_persister'
edit: sorry, the documentation actually says we have to do that, I missed it. My bad.
Problem solved

Load Symfony (5.2) config from database

I am a newbie in Symfony but I know how to use OOP in PHP.
I try (with frustration) to couple custom parameters with Symfony configs by using Doctrine entities.
To solve the problem I used for e.g. the answer from Michael Sivolobov: https://stackoverflow.com/a/28726681/2114615 and other sources.
My solution:
Step 1: Create new package in config folder
-> config
-> packages
-> project
-> services.yaml
-> project
-> src
-> ParameterLoaderBundle.php
-> DependencyInjection
-> Compiler
-> ParameterLoaderPass.php
Step 2: Import the new resource
# config/services.yaml
...
imports:
- { resource: 'packages/project/config/services.yaml' }
...
Step 3: Package coding
# packages/project/config/services.yaml
services:
Project\:
resource: "../src"
<?php
namespace Project;
use Project\DependencyInjection\Compiler\ParameterLoaderPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ParameterLoaderBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParameterLoaderPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
<?php
namespace Project\DependencyInjection\Compiler;
use App\Entity\SettingCategory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParameterLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$setting = $em->getRepository(SettingCategory::class)->findAll();
$container->setParameter('test', $setting);
}
}
After at all I test the new Parameter in my API controller:
$this->getParameter('Test');
But the following error message appears:
The parameter \"test\" must be defined.
Couple of things going on here. First off, loading config from a database is very unusual in Symfony so it is not surprising that you are having difficulty. Secondly, your process code is never getting called. Part of debugging is making sure that code that you expect to be called is in fact being called. Third, you really got off on a tangent with attempting to add a bundle under config. Way back in Symfony 2 there used to be more bundle related stuff under app/config and it may be that you discovered some old articles and misunderstood them.
But, the big problem here is that Symfony has what is known as a 'compile' phase which basically processes all the configuration and caches it. Hence the CompilerPassInterface. Unfortunately, services themselves are not available during the compile phase. They simply don't exist yet so no entity manager. You need to open your own database connection if you really want to load config from a database. You will want to use just a database connection object and not the entity manager since part of the compile phase is to process the entities themselves.
So get rid of all your code and just adjust your Kernel class:
# src/Kernel.php
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container)
{
$url = $_ENV['DATABASE_URL'];
$conn = DriverManager::getConnection(['url' => $url]);
$settings = $conn->executeQuery('SELECT * FROM settings')->fetchAllAssociative();
$container->setParameter('test',$settings);
}
And be aware that even if you get all this working, you will need to manually rebuild the Symfony cache after updating your settings table. It is not going to be automatic. You really might consider taking a completely different approach.

Read from parameters in Symfony 3.4 getParameter null

I can't read a parameter from parameters.yml in my controller.
I want to do this:
//My Controller
class ExampleController extends Controller
{
function someMethod($argument)
{
dump($this->getParameter('free_proxy'));die();
and in parameters.yml I got:
parameters:
free_proxy: "http://xxx:8080"
I get an error: Call to a member function getParameter() on null
I've tested some solutions like adding some services and using get and stuff but nothing works.
EDIT: also, I tried this:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$freeProxy: '%free_proxy%'
Then using:
$this->container->getParameter('free_proxy');
But I got an error: Unused binding "$freeProxy" in service...
So there are two mysteries here. First is why is the container not being injected which in turn causes getParameter to fail. And second, why does bind generate that unused binding error.
You did not show your routing but I suspect that somewhere along the line you actually have:
$exampleController = new ExampleController();
If so then this explains why getParameter is failing. You really need to let Symfony create the controller based on the route. Otherwise the container is not injected and other controller magic is skipped.
I installed a fresh 3.4 app with the old directory structure and added a parameter
composer create-project symfony/framework-standard-edition s34
# app/config/parameters.yml
parameters:
free_proxy: "http://xxx:8080"
I then tweaked the default controller using the default route annotation:
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$freeProxy = $this->getParameter('free_proxy');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR.$freeProxy,
]);
}
}
And everything worked as expected. The Symfony request handler takes care of injecting the container and thus gives you access to the parameters. If you cannot get this working then please update your question with your routing information.
I then took a look at the bind issue. You really want to inject these parameters instead of pulling them. I updated services.yml
# app/config/services.yml
services:
bind:
$freeProxy: '%free_proxy%'
And started getting those unused binding errors. It turns out that bind does not work for action injection. Not really sure why. I don't use it much but I really would have expected that just adding $freeProxy to your action method would work. In any event, here is a working example of the proper way to do things.
class ExampleController extends Controller
{
private $freeProxy;
public function __construct($freeProxy)
{
$this->freeProxy = $freeProxy;
}
/**
* #Route("/example", name="example")
*/
function someMethod()
{
dump($this->freeProxy);
dump($this->getParameter('free_proxy'));die();
}
}
I then went to a fresh 4.2 project and tried action injection:
class IndexController extends AbstractController
{
public function index($freeProxy)
{
return new Response("Index $freeProxy");
}
}
Action injection works as expected for 4.2 but not 3.4. Constructor injection works fine in either version.
documentation show like this :
parameters.yml :
parameters:
mailer.transport: sendmail
to set :
$container->setParameter('mailer.transport', 'sendmail');
to get :
$container->getParameter('mailer.transport');

Symfony 4 disable profiler in controller action

I have an action in the controller for mass instert in the database...
So this uses a lot of resources and the profiler is caching everything and server goes down.
How can i disable the profiler (and all the debug services) in one action on the controller?
The controller looks like :
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use App\Sync\Incomming\Syncronize;
/**
* #Route("/sync")
*/
class SyncController extends AbstractController
{
private $syncronize;
public function __construct(Syncronize $syncronize)
{
$this->syncronize = $syncronize;
}
/**
* #Route("/",name="sync_index")
*/
public function index(Request $request, Profiler $profiler)
{
$message = "Hello";
return $this->render( 'sync/output.html.twig', ['message' => $message ]);
}
}
if I try to autowire the profiler in the constructor method then I get the error public function __construct(Syncronize $syncronize, Profiler $profiler):
Cannot autowire service "App\Controller\SyncController": argument
"$profiler" of method "__construct()" references class
"Symfony\Component\HttpKernel\Profiler\Profiler" but no such service
exists. You should maybe alias this class to the existing "profiler"
service.
if I try to autowire the profiler in the index method then I get the error public function index(Request $request, Profiler $profiler):
Controller "App\Controller\SyncController::index()" requires that you
provide a value for the "$profiler" argument. Either the argument is
nullable and no null value has been provided, no default value has
been provided or because there is a non optional argument after this
one.
EDIT
For big queries disabling the profiler was not the solution... Actually you need to disable the setSQLLogger:
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Symfony 3.4 / 4
https://symfony.com/doc/4.0/profiler/matchers.html
use Symfony\Component\HttpKernel\Profiler\Profiler;
class DefaultController
{
// ...
public function someMethod(Profiler $profiler)
{
// for this particular controller action, the profiler is disabled
$profiler->disable();
// ...
}
}
If you have an error with autowiring
# config/services.yaml
services:
Symfony\Component\HttpKernel\Profiler\Profiler: '#profiler'
Old:
If you want to disable the profiler from a controller, you can like this:
use Symfony\Component\HttpKernel\Profiler\Profiler;
// ...
class DefaultController
{
// ...
public function someMethod(Profiler $profiler)
{
// for this particular controller action, the profiler is disabled
$profiler->disable();
// ...
}
}
Source: https://symfony.com/doc/current/profiler/matchers.html
Another way would be to use: $this->get('profiler')->disable();
Older:
Simply switch the app to prod mode and disable debug mode.
To do this, open the .env file on the server in your favourite editor (Note: You should never commit this file to Git, as you store secrets in there!).
In that file, you should see a section starting with: ###> symfony/framework-bundle ###
Just below that there is a APP_ENV=dev and APP_DEBUG=1, change these two lines to APP_ENV=prod and APP_DEBUG=0. Then save the file.
Next you should clear the cache for prod mode and install the assets. To do this, run the following commands:
php bin/console cache:clear --env=prod --no-debug
php bin/console cache:warmup --env=prod --no-debug
php bin/console assets:install --env=prod --no-debug --symlink
If you now load the application, it is in prod mode, which includes more caching and is faster as debug is disabled.
Note:
There will still be a timelimit for PHP. If you still hit that limit, you can either change your PHP setting or alternatively you could run the import from CLI, as CLI usually has no timelimit. If users need to be able to upload on their own, I'd suggest having them upload the file, enter a "note" about the file to a db and have a cronjob reading that db for not imported files and import them.

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

Resources