Symfony - Creating Own Config (service.yaml) Inside Bundle - php

I'm trying something so much time but maybe it's not even possible.
Sorry for my bad language.
So, I followed Symfony Doc https://symfony.com/doc/current/bundles.html to create new Bundle, and than I followed https://symfony.com/doc/current/bundles/extension.html to create DI extension.
My files:
AcmeHelloExtension.php
namespace App\Acme\HelloBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeHelloExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.xml');
}
}
AcmeHelloBundle.php
namespace App\Acme\HelloBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeHelloBundle extends Bundle
{
}
I added it to config/bundles.php
src/Acme/HelloBundle/Resources/config/services.yaml
services:
App\Acme\HelloBundle\AcmeHelloBundle:
tags: ['example-tags']
This service file isn't auto loaded, do I need to do next steps or it should work? When I check it with debug:container ....bundle...
Option-Tags has empty value. When I put this code in config/services.yaml it works.

The basic problem was that the bundle's source code was located under the project's src directory:
project
src
Ztest
ZtestBundle.php
This in turn was causing the bundle's services to be be autowired by the application's config/services.yaml file:
# project\config\services
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
...
- '../src/Ztest' # Add this to fix the problem
Excluding the bundle's source code fixed the issues. Autowiring at the application level overrides any manual wiring done at the bundle level.
Of course in general if you do decide you need a bundle then it's source code should be in it's own independent directory:
project
src
src-ztest-bundle
For this to work you also need to update the psr-4 section of composer.json and run "composer dump-autoload".
Keep in mind that in Symfony 4+, the only recommended usage of custom bundles is for code shared across multiple Symfony applications. In which case, the bundle should ultimately end up in it's own repository and composer package.
However, custom bundles inside of an application are still supported and there are times where they come in useful.

Related

How to fix '...SomeController has no container set' on Controllers defined in Symfony 5 bundle?

I have created a custom Symfony 5.3 bundle to share code between different projects. In src/controller/SomeController.php the bundle implements a controller class which extends from Symfony\Bundle\FrameworkBundle\Controller\AbstractController.
When accessing this controller via a route in my Symfony project I get the following error:
"XY\CommensBundle\Controller\SomeController" has no container set, did
you forget to define it as a service subscriber?
AbstractController has a setContainer method which is used to inject the service container. On controllers implemented directly in my Symfony project this method is called automatically by autowire / autoconfigure.
However, regarding to the Symfony docs autowire / autoconfigure should not be used for bundle services. Instead, all services should be defined explicitly. So I added this to the bundles services.yaml:
# config/services.yaml
services:
xy_commons.controller.some_controller:
class: XY\CommensBundle\Controller\SomeController
public: false
calls:
- [ setContainer, [ '#service_container' ]]
After adding the bundle to my Symfony project using Composer the console shows, that the controller is correctly added as a service. Everything seems fine.
php bin/console debug:container 'xy_commons.controller.some_controller'
Information for Service "xy_commons.controller.some_controller"
=============================================================
---------------- -------------------------------------------------------
Option Value
---------------- -------------------------------------------------------
Service ID xy_commons.controller.some_controller
Class XY\CommensBundle\Controller\SomeController
Tags -
Calls setContainer
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autoconfigured no
---------------- -------------------------------------------------------
However, the error is still the same. So how to configure controllers / services in Bundles correctly?
EDIT:
SomeController is just a very basic subclass of AbstractController:
class SomeController extends AbstractController {
public function some(): Response {
return new Response("<html><body>OK</body></html>");
}
}
Fully-qualified class name as Service ID
Usually I use FQCNs as Service ID. However, in this case I followed the advise from the Symfony docs (linked above) which explicitly say not to do so:
If the bundle defines services, they must be prefixed with the bundle
alias instead of using fully qualified class names like you do in your
project services. For example, AcmeBlogBundle services must be
prefixed with acme_blog. The reason is that bundles shouldn’t rely on
features such as service autowiring or autoconfiguration to not impose
an overhead when compiling application services.
In addition, services not meant to be used by the application
directly, should be defined as private.
This is way I used a snake name as ID and made the service private. The controller is not really used as service but only created automatically by Symfony when accessing / calling it via a route.
I recently struggle with the same problem. This is the solution.
Suppose we have the Vendor/FooBundle bundle.
Controller with route annotations
<?php
namespace Vendor\FooBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class EntryCategoryController extends AbstractController
{
/**
* #Route("/")
*/
public function list()
{
$entryCategories = [];
return $this->render('#VendorFoo/entry_category/list.html.twig', [
'entryCategories' => $entryCategories
]);
}
}
Register controller as service. The service id must be the controller fully-qualified class name, not a snake case string like vendor_foo.controller.entry_category. This is the key thing! Eventually you can create an alias for the controller service, but this is redundant.
# Resources/config/services.yaml
services:
# The service id must be the controller fully-qualified class name.
# If it a snake case string like `vendor_foo.controller.entry_category`,
# then an alias must be created.
# Vendor\FooBundle\Controller\EntryCategoryController:
# alias: '#vendor_foo.controller.entry_category'
# public: false
Vendor\FooBundle\Controller\EntryCategoryController:
# Setting class is redundant, but adds autocompletions for the IDE.
class: Vendor\FooBundle\Controller\EntryCategoryController
arguments:
# Add this tag to inject services into controller actions.
tags: ['controller.service_arguments']
# Call the setContainer method to get access to the services via
# $this->get() method.
calls:
- ['setContainer', ['#service_container']]
Routing file.
# Resources/config/routing.yaml
vendorfoo_entrycategories:
resource: '#VendorFooBundle/Controller/EntryCategoryController.php'
type: annotation
prefix: /entry-categories
Import routing file in your app
# config/routes.yaml
vendor_foo:
resource: '#VendorFooBundle/Resources/config/routing.yml'
prefix: /foo
That's all.
So #ArturDoruch has the correct main points. You should use the fully qualified class name as the service id though I suppose you could use snake case as long as you also used it in your routes file. But there is no particular reason not to use the class name.
Your controller service also needs to be public so the controller resolver can pull it from the DI container. If the service is not public then the resolver just tries to new the controller and will never call set container or inject any constructor args. That is why you get the error about no container set. By the way, a magical side effect of tagging the service with controller.service_arguments is that the service becomes public.
The thing is that using controllers in bundles is just not something you see much anymore mostly because it's a pain if application want to slightly tweak the way controller works.
So if you look in the best practices you see things like bundle controllers should not extend AbstractConroller and bundles should not use autowire. Good advice for most bundles but if you just want to get stuff working then it's easy to get bogged down.
I would suggest starting with the same services.yaml file that comes with the application. You need to tweak the paths slightly:
# Resources/config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
MyBundle\:
resource: '../../'
exclude:
- '../../Resources/'
- '../../DependencyInjection/'
- '../../Entity/'
# bin/console debug:container MyController
Service ID MyBundle\Controller\MyController
Class MyBundle\Controller\MyController
Tags controller.service_arguments
container.service_subscriber
Calls setContainer
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
Now you can focus on just getting your bundle to work. Let autowire do the heavy lifting. Once the bundle has stabilized then maybe you can go back in and start to manually define your services as recommended in the best practices. Or maybe not.

Symfony4 use external class library as a service

I have a little external library that expose many classes.
Into my symfony4 project I would like to declare my class from vendor, as a service with autowire and public.
So I have include my library with composer and add psr configuration like this into composer.json:
"autoload": {
"psr-4": {
"App\\": "src/",
"ExternalLibrary\\": "vendor/external-library/api/src/"
}
}
After that I have tried to change my services.yaml into symfony like this:
ExternalLibrary\:
resource: '../vendor/external-library/api/src/*'
public: true
autowire: true
If I launch tests or run the application returns me this error:
Cannot autowire service "App\Domain\Service\MyService": argument "$repository" of method "__construct()" references interface "ExternalLibrary\Domain\Model\Repository" but no such service exists. You should maybe alias this interface to the existing "App\Infrastructure\Domain\Model\MysqlRepository" service.
If I declare into services.yaml the interface this works fine:
ExternalLibrary\Domain\Model\Lotto\Repository:
class: '../vendor/external-library/api/src/Domain/Model/Repository.php'
public: true
autowire: true
But I have many classes and I don't want to declare each class, how can I fix services.yaml without declare every single service?
Thanks
You need to create services by hand:
I did not test it but it should look like this
services.yaml
Some\Vendor\:
resource: '../vendor/external-library/api/src/*'
public: true # should be false
Some\Vendor\FooInterface:
alias: Some\Vendor\Foo # Interface implementation
Some\Vendor\Bar:
class: Some\Vendor\Bar
autowire: true
php
<?php
namespace Some\Vendor;
class Foo implements FooInterface
{
}
class Bar
{
public function __construct(FooInterface $foo)
{
}
}
To be more precise you should have something like
ExternalLibrary\Domain\Model\Repository:
alias: App\Infrastructure\Domain\Model\MysqlRepository
Let's take Dompdf as an example :
When you try to add type-hint Dompdf in your action controller or service method , an error will be occurred saying that auto-wiring isn't possible because Dompdf is an external PHP library
So to solve this problem we'll make a little change in our services.yaml file by adding this short config
Dompdf\: #Add the global namespace
resource: '../vendor/dompdf/dompdf/src/*' #Where can we find your external lib ?
autowire: true #Turn autowire to true
Apply the above example to all external PHP libs :)
That's all !
I had the same problem and someone gave me this solution, which works fine for me:
Use an external repository with symfony4 trouble with autoload and parameters
I copy the other solution by user #DasBen here just in case:
I think that you don't have to import each service separately. Your are already doing that with the "Puc\SapClient" part.
The problem could be that you are importing your models, which should not be imported.
In the symfony example project there is this part vor "services.yaml":
# 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/{Bundle,DependencyInjection,Entity,Model,Migrations,Tests,Kernel.php}'
Then your part would be:
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Puc\SapClient\:
resource: '../vendor/puc/sap-client/src/*'
exclude: ''../vendor/puc/sap-client/src/{Entity,Model,"etc."}'
"etc." Would be everything that is not needed as service.

Symfony 4 _instanceof in Bundle's services.yaml

I have a bundle which has interface Optimax\HealthCheckBundle\Service\HealthInterface
I need set tags to all services which implement this interface. I do it with the following directive:
_instanceof:
Optimax\HealthCheckBundle\Service\HealthInterface:
tags: ['health.service']
It works fine when I put this directive into config/services.yaml. But if I put this code into my bundle's config (which required via composer) vendor/optimax/health-check/src/Resources/config/services.yaml it doesn't work. I don't want copy-paste this directive into services.yaml every time when I require this bundle to a new project.
How can I move this directive into services.yaml which is in my Bundle's directory or at least into another file in config/packages folder of the project?
To expand on the issue for others.
_instanceof functions identically to the _defaults definition. In that the _instanceof definition only applies to the file it is used in.
This prevents third-party bundle definitions from affecting your entire application with definitions like:
_defaults:
public: true
autowire: false
_instanceof:
Psr\Log\LoggerAwareInterface:
- method: setLogger
arguments:
- '#custom_logger'
tags:
- { name: monologer.log, channel: 'custom_channel' }
Therefor if the service you are attempting to tag with _instanceof is not declared in the same services.yml file the tag will not be added.
To tag services that implements an Interface in the entire application, you would need to use the method provided by #Jeroen
Did you try auto tagging all services with this interface in your Bundle extension like this:
$container->registerForAutoconfiguration(CustomInterface::class)
->addTag('app.custom_tag')
;
Taken from the Symfony docs:
https://symfony.com/doc/current/service_container/tags.html

Symfony not overriding bundle view

I have been following the Symfony docs intending to override the Sylius Web Bundle layout.html.twig using inheritance.
The bundle file is at
/vendor/sylius/sylius/src/Sylius/Bundle/WebBundle/Resources/views/Frontend/layout.html.twig
I've placed a new file at
/src/AppBundle/Resources/views/Frontend/layout.html.twig
I have also updated the file at:
/src/AppBundle/AppBundle.php
to inherit the relevant bundle
<?php
namespace AppBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function getParent()
{
return 'SyliusWebBundle';
}
}
But the page stays the same. If I remove the original layout.html.twig, Symfony says there it cannot find the file. It never attempts to find my new file.
Does this configuration look correct? Is there a common misconfiguration which could be preventing this? Stepping through the debugger I can see that the getParent() function is being hit, but is there any other way I could debug this problem?
Very likely it is just cache.
The code you provided is correct, and it works for me it the exact same config.
so
> php app/console cache:clear
should be all that is needed
One other silly thing that it could be .. did you added your appbundle to appKernel.php ??
Try to place the template at:
app/Resources/SyliusWebBundle/views/layout.html.twig

Symfony services container is imported but how?

I have to make modifications on a Symfony project and I am currently wondering how this project works.
It has a services.php file in Company/Bundle/Resources/config that manages the instantiation of services, but it is not imported anywhere...
I looked in app/config/config.yml, config_dev.yml, config_prod.yml, security.yml, etc and there are no imports for this file!
I know it used by the application, but I have no idea how it is imported :/.
Does it make use of the DependenctInjection service? Have a look at the docs here
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');
This will autoload the service when the symfony app is initialized.

Categories