threeside
threeside

Reputation: 358

Symfony 5 service not load

I got a class use to upload file as service like symfony documentation. https://symfony.com/doc/current/controller/upload_file.html#creating-an-uploader-service

I use symfony 5.

When i declare service in main config/services.yaml, this work.

But i have a bundle for file management and i want to put service declaration in this bundle : App/AD/ExplorerBundle/Resources/config/services.yaml.

When i do that this doesn't work anymore.

I have error

Cannot resolve argument $fileUploader of "App\AD\ExplorerBundle\Controller\FileController::addAction()": Cannot autowire service "App\AD\ExplorerBundle\Service\FileUploader": argument "$targetDirectory" of method "__construct()" is type-hinted "string", you should configure its value explicitly.

I don't understand why, because _defaults autoconfigure and autowire = true

I test cache:clear, reload server, but nothing work.

Any help will be apreciate

Edit : my bundle extension: in AD\ExplorerBundle\DependencyInjection

<?php

namespace App\AD\ExplorerBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

/**
 * This is the class that loads and manages your bundle configuration.
 *
 * @link http://symfony.com/doc/current/cookbook/bundles/extension.html
 */
class ADExplorerExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');
    }
}

my bundle service : in AD\ExplorerBundle\Service

<?php
namespace App\AD\ExplorerBundle\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(string $targetDirectory, SluggerInterface $slugger)
    {
        $this->targetDirectory = $targetDirectory;
        $this->slugger = $slugger;
    }

    public function upload(UploadedFile $file): array
    {
        $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
        $safeFilename = $this->slugger->slug($originalFilename);
        $fileName = $safeFilename.'-'.uniqid().'.'.$file->guessExtension();

        $result = array(
            'filename' => $fileName,
            'originalName' => $originalFilename,
            'extension' => $file->guessExtension()
                );

        try {
            $file->move($this->getTargetDirectory(), $fileName);
        } catch (FileException $e) {
            // ... handle exception if something happens during file upload
        }

        return $result;
    }

    public function getTargetDirectory()
    {
        return $this->targetDirectory;
    }
}

my config/services.yaml

parameters:
    locale: 'fr'
    doctrine_behaviors_translatable_fetch_mode: "LAZY"
    doctrine_behaviors_translation_fetch_mode: "LAZY"



imports:
    - { resource: '@ADCoreBundle/Resources/config/services.yml' }
    - { resource: './parameters.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.

    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'


    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

my Bundle service : in AD\ExplorerBundle\Resources\config\service.yaml

parameters:
    brochures_directory: '%kernel.project_dir%/public/uploads'

services: 
    ad_file_uploader:         
        class: App\AD\ExplorerBundle\Service\FileUploader
        arguments:
            $targetDirectory: '%brochures_directory%'

I read documentation : https://symfony.com/doc/current/bundles/extension.html

https://symfony.com/doc/current/service_container.html#manually-wiring-arguments

https://symfony.com/doc/current/service_container/autowiring.html

I don't really understand this :

Public and Reusable Bundles¶

Public bundles should explicitly configure their services and not rely on autowiring. Autowiring depends on the services that are available in the container and bundles have no control over the service container of applications they are included in. You can use autowiring when building reusable bundles within your company, as you have full control over all code.

I think it's ok because it's my bundle and my application so i have full control of code.

So, i test a lot of thing but nothing work.

If anybody have an idea Thanks

Upvotes: 2

Views: 5583

Answers (1)

Cerad
Cerad

Reputation: 48883

Symfony recommends using manual service definitions in bundles mostly to avoid the overhead of constantly scanning the bundle everytime the cache is rebuilt. But I thought it might be interesting to see what creating a potentially reusable bundle with autowire/autoconfigure actually entails. Tried to follow the bundles best practices as much as I could.

For my own reference, I checked in a complete working bundle example.

Ultimately bundles should end up in their own repository. However, it can be easier to develop a bundle by enclosing it inside an application. This is the approach I used. But it is important not to try and mix your app source code with the bundle source code. Doing so is not only challenging but will make it difficult to copy your bundle into it's own repository.

So we start with a directory structure like this:

project # Symfony application project
    src: # Application source code
    explorer-bundle # AD Explorer Bundle source code
        ADExplorerBundle.php

Getting your namespace right is also important. The prefix really should be a unique vendor itdentifier just to avoid possible naming conflicts. In this case, we use AD. And then of course, since AD might have multiple bundles, we further sub-divide with a bundle specific identifier.

namespace AD\ExplorerBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class ADExplorerBundle extends Bundle
{

}

In order for autoloading to work we tweak the composer.json file. Once the bundle is converted into a true composer package, this line will no longer be needed. And of course you have to add the bundle class to project/config/bundles.php

# composer.json
    "autoload": {
        "psr-4": {
            "App\\": "src/",
            "AD\\ExplorerBundle\\": "explorer-bundle/"
        }
    },
# run composer "dump-autoload" after making this change

So now we need an extension to load the bundle's services.yaml. It's just a standard load so no need to show the code here. This is what the services.yaml file looks like:

# explorer-bundle/Resources/config/services.yaml
parameters:
    # Cheating here, this would normally be part of the bundle configuration
    ad_explorer_target_directory: 'some_directory'

services:
    _defaults:
        autowire: true
        autoconfigure: true
        bind:
            $targetDirectory: '%ad_explorer_target_directory%'

    AD\ExplorerBundle\:
        resource: '../../*'
        exclude: '../../{DependencyInjection,Entity,Migrations,Tests,ADExplorerBundle.php}'

    AD\ExplorerBundle\Controller\:
        resource: '../../Controller'
        tags: ['controller.service_arguments']

To keep things simple, I just made the target directory a parameter. You would probably want to make an actual bundle configuration and give the application the ability to set it. But that is outside the scope of this answer.

For testing I chose to create a command. I find it easier than trying to refresh the browser.

// Easier to debug an injected service using a command than a controller
class ExplorerAddCommand extends Command
{
    private $fileUploader;

    protected static $defaultName = 'ad:explorer:add';

    public function __construct(FileUploader $fileUploader)
    {
        parent::__construct();
        $this->fileUploader = $fileUploader;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        echo "Explorer Add " . $this->fileUploader->targetDirectory . "\n";
        return 0;
    }
}

And that is it.

Upvotes: 3

Related Questions