Symfony service FileUploader not autowiring - symfony

I've followed the Symfony 5.2 tutorial to add a FileUploader as a service (https://symfony.com/doc/current/controller/upload_file.html).
So this is my service.yaml
parameters:
targetDirectory: '%kernel.project_dir%/public/uploads/'
previews_video: '%kernel.project_dir%/public/uploads/previews'
brochures_directory: '%kernel.project_dir%/public/uploads/brochures'
services:
App\Service\FileUploader:
arguments:
$targetDirectory: '%previews_video%'
And this is my FileUploader.php
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\String\Slugger\SluggerInterface;
class FileUploader
{
private $targetDirectory;
private $slugger;
public function __construct($targetDirectory, SluggerInterface $slugger)
{
$this->targetDirectory = $targetDirectory;
$this->slugger = $slugger;
}
public function upload(UploadedFile $file)
{
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugger->slug($originalFilename);
$fileName = $safeFilename.'-'.uniqid().'.'.$file->guessExtension();
try {
$file->move($this->getTargetDirectory(), $fileName);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
return $fileName;
}
public function getTargetDirectory()
{
return $this->targetDirectory;
}
}
But I'm having this common error :
Cannot resolve argument $fileUploader of "App\Controller\VideoController::edit()": Cannot autowire service "App\Service\FileUploader": argument "$targetDirectory" of method "__construct()" has no type-hint, you should configure its value explicitly.
Called by this controller :
/**
* #Route("/{id}/edit", name="video_edit", methods={"GET","POST"})
* #param Request $request
* #param Video $video
* #param FileUploader $fileUploader
* #return Response
*/
public function edit(Request $request, Video $video, FileUploader $fileUploader): Response
{...}
How do I fix this ? I trying by remove the string type, adding the string type, removing the $ from the targetDirectory parameters in services.yaml... Struggling with that for hours now...

Take a look at my working services.yaml. I've changed the namespace
App\Service
to
App\Services
And I also added the service declaration at the end of the file.
Looks like the order of the lines in services matter. First, I've added the declaration at the top of the services part, but the autowiring is declared after, guess the error was here...
# 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/configuration.html#application-related-configuration
parameters:
previews_directory: '%kernel.project_dir%/public/uploads/previews'
services:
#i've added my service here at first...
app.menu_builder:
class: App\Menu\MenuBuilder
arguments: ["#knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main }
# 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'
- '../src/Tests/'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Services\FileUploader:
arguments:
$targetDirectory: '%previews_directory%'

You should have autowiring configuration added to your services file:
parameters:
targetDirectory: '%kernel.project_dir%/public/uploads/'
previews_video: '%kernel.project_dir%/public/uploads/previews'
brochures_directory: '%kernel.project_dir%/public/uploads/brochures'
services:
# To add:
_defaults:
autowire: true
autoconfigure: true
# You service
App\Service\FileUploader:
arguments:
$targetDirectory: '%previews_video%'

Add a type-hint string for $targetDirectory in the contructor
public function __construct(string $targetDirectory, SluggerInterface $slugger)
{
$this->targetDirectory = $targetDirectory;
$this->slugger = $slugger;
}

I had the same issue.
It was related to the indentation of that specific service. I wasn't getting any indentation error but also the auto wiring wasn't working.
What i did was to add 4 spaces as indentation
App\Service\FileUploader:
arguments:
$targetDirectory: '%TEAM_LOGO_DIRECTORY%'

Yes I got the problem too and I managed to solve it by replacing indentation by spaces in the services.yaml file, I added all of these properties at the same root and then I did that and it works for me:
services:
App\Services\FileUploader: #(added 4 spaces, NOT 1 tab)
arguments: #(added 8 spaces, NOT 2 tabs)
$targetDirectory: '%cats_directory%' #(added 12 spaces, NOT 3 tabs)
If you struggle you (and other people who got this problem) can try this solution. I don't guarantee it will work 100%.

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

Symfony 3.4 NotFoundHttpException No route found for "GET /lucky/number"

I'v created a symfony 3.4 project,and I've create a controller following the doc (I had to change the controller namespace)
<?php
namespace App\Controller;
//namespace AppBundle\Controller; this is the default namespace in the doc
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
/**
* #Route("/lucky/number")
*/
public function numberAction()
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
But when I try to access to http://127.0.0.1:8000/lucky/number I get this error:
NotFoundHttpException No route found for "GET /lucky/number"
I tried to clean the cache but didn't work and think I dont have to download any anootations library, I don't know what is wrong
If you changed the namespace you also need to make some changes to your config.
You can tag the individual controller with controller.service_arguments:
# app/config/services.yml
services:
# ...
# explicitly configure the service
App\Controller\LuckyController:
tags: [controller.service_arguments]
Or if you have changed the entire AppBundle namespace to App and you are using the Symfony Standard Edition (version 3.4) services.yml configuration, just change all instances of AppBundle to App:
# app/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
public: false
# makes classes in src/App available to be used as services
App\:
resource: '../../src/App/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/App/{Entity,Repository}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
App\Controller\:
resource: '../../src/App/Controller'
public: true
tags: ['controller.service_arguments']
Of course always clear the cache after making any changes.

Symfony 4 services local binding in different environments

I have to bind parameters with different values in different environments, and having problems with this.
I was trying this:
# config/services.yaml
services:
_defaults:
bind:
$param: 'param for PROD'
# config/services_dev.yaml
services:
_defaults:
bind:
$param: 'param for DEV'
# src/Controller/SomeController.php
class MyController extends AbstractController
{
public function example($param)
{
echo $param;
}
}
But it forces me to have all the services defined in both of services.yaml and services_dev.yaml files, otherwise it does not work.
I would like to have a services.yaml shared for any environment, and only override the custom services/bindings etc, not have two identical files with all services listed in them for changing one binding value.
The real problem is that I have to create two http clients (real and a dummy) with same interface, in production load the real one, and in development load the dummy, Symfony 4-s autowiring allows me to inject the interface in a controller and choose which client to use in binding:
# config/services.yaml
services:
_defaults:
bind:
'ClientInterface': '#real_client'
# More services here...
# config/services_dev.yaml
services:
_defaults:
bind:
'ClientInterface': '#dummy_client'
# Here I don't want to have another copy of the services,
# but it does not work without them
# Controller
public function someMethod(ClientInterface $client)
{
// ...
}
In Symfony 2 I was able to extend services.yml and in services_dev.yml only define the specific values I wanted to override/add, but in Symfony 4 services_dev.yaml can not use services from services.yaml and I have to keep my services identical in two different files which is pain.
Anny suggestions?
Thank you.
I'm updating the post again with a real example:
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/configuration.html#application-related-configuration
parameters:
locale: 'en'
app.access_token: '%env(string:APP_ACCESS_TOKEN)%'
app.aws_version: '%env(string:AWS_VERSION)%'
app.aws_profile: '%env(string:AWS_PROFILE)%'
app.aws_region: '%env(string:AWS_REGION)%'
app.aws_queue_url_creation: '%env(string:AWS_QUEUE_URL_CAMPAIGN_CREATION)%'
app.aws_queue_url_edition: '%env(string:AWS_QUEUE_URL_CAMPAIGN_EDITION)%'
app.redis_host: '%env(string:REDIS_HOST)%'
app.redis_port: '%env(string:REDIS_PORT)%'
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
App\Service\MessageSenderServiceInterface: '#App\Service\MessageSenderSqsService'
# 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,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
# Authenticators
App\Security\ApiKeyAuthenticator:
arguments:
- "%app.access_token%"
# Clients
App\Client\AwsSqsClient:
arguments:
- "%app.aws_version%"
- "%app.aws_profile%"
- "%app.aws_region%"
App\Client\RedisClient:
arguments:
- "%app.redis_host%"
- "%app.redis_port%"
# Services
App\Service\MessageSenderSqsService:
arguments:
- '#App\Client\AwsSqsClient'
- '#App\Client\RedisClient'
- "%app.aws_queue_url_creation%"
- "%app.aws_queue_url_edition%"
App\Service\MessageSenderRedisService:
arguments:
- '#App\Client\RedisClient'
services_dev.yaml
imports:
- { resource: services.yaml }
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
App\Service\MessageSenderServiceInterface: '#App\Service\MessageSenderRedisService'
Controller.php
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
/**
* #Route("/api/dummy")
*/
public function dummyEndpoint(MessageSenderServiceInterface $messageSender)
{
echo get_class($messageSender); exit;
}
}
And the echo from controller for both envs (prod and dev) is
App\Service\MessageSenderSqsService
But if I copy whole node "services" form services.yaml to services_dev.yaml and only change the binding config, it works fine and says that the injected class is:
App\Service\MessageSenderRedisService
I've just noticed that if I don't touch the "_defaults" node, it works as expected, the problems start only when I want to override the _defaults node of services...
You can define parameters in parameters section of config.yml and overwrite this parameters in config_dev.yml.
# config.yml
imports:
# ...
parameters:
parameter_1: value 1
parameter_2: value 2
# ...
framework:
# ...
# config_dev.yml
imports:
# ...
parameters:
parameter_1: dev value 1
# ...
framework:
# ...
This parameters can be used used in service.yml as:
# service.yml
services:
_defaults:
bind:
$param: '%parameter_1%'
Finally the problem was only in overriding the "_defaults" node (which I was touching in order to have different "bind" configs in the project).
Extending services.yaml without overriding _defaults, everything works as expected. And the solution is to have different configuration for services with their bindings by environment, and have "_defaults" only in services.yaml.
If we override the "_defaults" in other files, we'll have to redefine all the services too.
Thanks everyone for help.
You have some options:
1.Don't use bind and write different service configs for different environments
# services.yaml
App\Controller:
arguments:
- "#client"
# services_dev.yaml
App\Controller:
arguments:
- "#dummy_client"
2.Use bind and create service alias in each environment's services.yaml:
# services.yaml
services:
some.client:
alias: "#client"
# services_dev.yaml
services:
some.client:
alias: "#dummy_client"
3.Just configure only one ClientInterface service per environment:
# services.yaml
App\ClientInterface:
class: App\RealClient
# services_dev.yaml
App\ClientInterface:
class: App\DummyClient
4.Use factory which will create this client depends on environment (but this is not very good practice as for me)
# services.yaml
App\ClientInterface:
factory: ["#App\ClientFactory", create]
arguments:
- '%kernel.environment%'
class ClientFactory
{
public function create(string $env): ClientInterface
{
if ($env === 'dev') {
return new DummyClient();
} else {
return new Client();
}
}
}
5.In your case, when you have so much services and you want to inject same service in all of them, you can use option #3 or you can create one interface for all of them and use _instanceof:
# services.yaml
_instanceof:
App\SomeCommonInterface:
calls:
- method: setSomeService # interface's method
arguments:
- '#service'
# services_dev.yaml
_instanceof:
App\SomeCommonInterface:
calls:
- method: setSomeService
arguments:
- '#dummy_service'

Service autowire don't work with Symfony 4

I try to pass from Symfony 3.4 to Symfony 4.1, but I've a problem with autowire. I've the symfony/swiftmailer-bundle installed, and in an event subscriber I have:
public function __construct(\Swift_Mailer $mailer, EngineInterface $templating, EntityManagerInterface $em, $senderMail, $senderName)
{
$this->mailer = $mailer;
$this->templating = $templating;
$this->em = $em;
$this->senderMail = $senderMail;
$this->senderName = $senderName;
}
In the service.yaml:
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# 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/{Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
# Twig
twig.extension.text:
class: Twig_Extensions_Extension_Text
tags:
- { name: twig.extension }
# Listeners
App\EventListener\ContactNotificationSubscriber:
$senderMail: '%env(MAILER_SENDER_ADDRESS)%'
$senderName: '%env(MAILER_SENDER_NAME)%'
But I've an error:
Cannot autowire service "App\EventListener\ContactNotificationSubscriber": argument "$mailer" of method "__construct()" references class "Swift_Mailer" but no such service exists.
I don't understand why... The component exists, with PhpStorm, I can click on \Swift_Mailer and see the class, but Symfony always return to me an error...
If someone know why :-) Thanks a lot
I was having the same problem. For my case, the bundle was not being included in bundles.php. Adding the following in bundles.php solved it for me:
Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
You can change
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
to :
App\:
resource: '../src/*'
exclude: '../src/{Entity,EventListener,Migrations,Tests,Kernel.php}'

SymfonyBundle: How to pass to a controller as a service the full bundle configuration

I've created a bundle completely separate from the main app, so I install it via Composer.
This bundle requires some sort of configuration:
# app/config/config.yml
shq_mybundle:
node1:
node1_1:
...
node1_2:
...
node2:
node2_1:
...
node2_2:
...
My bundle also has a controller MyBundleAController and this controller has a ––construct() with this signature:
class MyBundleAController
{
public function __construct(EntityManagerInterface $entityManager, EventDispatcherInterface $eventDispatcher, array $config)
{
$this->entityManager = $entityManager;
$this->eventDispatcher = $eventDispatcher;
$this->config = $config;
}
}
My bundle also loads a services.yml file that uses autowiring to configure controllers:
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to d
SerendipityHQ\Bundle\MyBundle\Controller\:
resource: '../../Controller/*'
public: false
tags: ['controller.service_arguments']
Obviously, the way MyBundleAController is configured throws an error, as the $config argument is unknown by the autowiring functionality that requires the argument be typehinted or explicitly set:
Cannot autowire service
"SerendipityHQ\Bundle\MyBundle\Controller\MyBundleAController":
argument "$config" of method "__construct()" must have a type-hint or
be given a value explicitly.
And here we arrive to my question: the $config parameter is the one that someone sets in its app/config/config.yml, so this one:
# app/config/config.yml
shq_mybundle:
node1:
node1_1:
...
node1_2:
...
node2:
node2_1:
...
node2_2:
...
How can I pass shq_mymodule configuration to the autowired controller?
In a first attempt, I've tried to do something like this
SerendipityHQ\Bundle\MyBundle\Controller\ConnectController:
arguments:
$config: "%shq_mybundle%"
But obviously this doesn't work.
To make it work I should do something like this in MyBundleExtension:
$container->setParameter('shq_mybundle', $config);
This way I transform it in a parameter accessible from the services.yml file that can use it to autowire the MyBundleAController controller.
But this seems to me like an hack: is there a more elegant way to do this?

Resources