Manage Common Dependencies with Parent Services in symfony 3 - php

I noticed a strange thing with managing common dependencies with parent services in symfony 3.1.6.
http://symfony.com/doc/current/service_container/parent_services.html
I try to define a user_manager service which extends fos user manager service.
// AppBundle/Service/UserManager.php
namespace AppBundle\Service;
use FOS\UserBundle\Doctrine\UserManager as BaseUserManager;
class UserManager extends BaseUserManager
{
}
// AppBundle/DependencyInjection/AppExtension.php
namespace AppBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class AppExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
# AppBundle/Resources/config/services.yml
services:
app.user_manager:
class: AppBundle\Service\UserManager
parent: fos_user.user_manager.default
When trying to get service in contoller
$this->get('app.user_manager');
Then the exception is thrown:
'You have requested a non-existent service "app.user_manager".'
However, if I define my custom service in a different way:
# AppBundle/Resources/config/services.yml
services:
app.user_manager:
class: AppBundle\Service\UserManager
arguments:
- '#fos_user.util.password_updater'
- '#fos_user.util.canonical_fields_updater'
- '#fos_user.object_manager'
- '%fos_user.model.user.class%'
Then it works fine.
Moreover, it used to work like this with a "parent" before, in symfony 2.8
What could be wrong here and how it should be done properly with symfony 3.1?
p.s. I tried to clear cache, and also remove var/cache* directory, but that doesn't help

Related

Error when loading parameters into Symfony bundle

I get following error:
There is no extension able to load the configuration for
"upload_images" (in
"/var/www/vhosts/diabetigraph-dev/vendor/verzeilberg/upload-images/src/Resources/services.yaml").
Looked for namespace "upload_images", found "none"
This are my files:
services.yaml
services:
verzeilberg\UploadImagesBundle\Service\Rotate:
autowire: true
upload_images:
version: 100
Configuration
namespace verzeilberg\UploadImagesBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('upload_images');
$treeBuilder->getRootNode()
->children()
->integerNode('version')->end()
->end();
return $treeBuilder;
}
}
UploadImagesExtension
namespace verzeilberg\UploadImagesBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
class UploadImagesExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources'));
$loader->load('services.yaml');
$config = $this->processConfiguration(new Configuration(), $configs);
$container->setParameter('version', $config['version']);
}
}
What am I doing wrong?
The basic answer is that your upload_images configuration needs to be moved to it's own application level config/packages file:
# app/config/packages/upload_images.yaml
upload_images:
version: 100
In your bundle, the Configuration object represents the bundle's configuration. There is no upload_images.yaml sort of file in your bundle. The object is quite powerful so you can add defaults and options and what not.
Your bundle's extension takes care of processing the final configuration and making information such as parameters available to the rest of the system:
class UploadImagesExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// $configs is actually an array of array representing the contents of upload_files.yaml
$config = $this->processConfiguration(new Configuration(), $configs);
$container->setParameter('upload_files.version', $config['version']);
// Loading your bundle's services.yaml file is a different process which just happens to be kicked off by the loader
// Thanks to the setParameter above, %upload_files.version% can be used by services
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources'));
$loader->load('services.yaml');
}
}
It can be confusing and for me at least I had to read the docs multiple times and do quite a bit of experimenting to understand the overall process.
The confusion is one of the reasons that Symfony evolved from multiple bundles per application to one application bundle to no application bundle at all.
I might also add the Symfony uses composer recipes to simplify installing a bundle. The recipe not only adds the bundle to config/bundles.php but copies any default configuration file to config/packages as well. Unfortunately, there are additional steps required for this copy to happen (see the docs) so it's easiest to do things the old-fashion way and just tell the developer to create the config file via the bundle's README file.

Problem with autowiring RegistryInterface in ServiceEntityRepository

I was following Symfony docs and wanted to use ServiceEntityRepository (link).
I created entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BetRepository")
*/
class Bet
{
....
Repository was automatically created:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use App\Entity\Bet;
class BetRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Bet::class);
}
}
I wanted to create BetService which uses BetRepository:
namespace App\Service;
use App\Repository\BetRepository;
final class BetService
{
private $betRepository;
public function __construct(BetRepository $betRepository)
{
$this->betRepository = $betRepository;
}
finally I created a controller and wanted to inject BetService into it:
namespace App\Controller;
use App\Service\BetService;
final class BetController extends AbstractController
{
private $betService;
public function __construct(BetService $betService)
{
$this->betService = $betService;
}
}
Problem is I keep getting error:
Cannot autowire service "App\Repository\BetRepository": argument
"$registry" of method "__construct()" references interface
"Symfony\Bridge\Doctrine\RegistryInterface" but no such service
exists. Did you create a class that implements this interface?
my services.yaml is default one from installation:
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.
public: false
# 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']
I am using Symfony 4.2
I tried autowiring EntityManager to BetRepository however I get error that EntityManager constructor is not public and tbh I feel this is not the right approach as docs say it should work out of box.
I can provide composer.json (didn't initially as already question is quite long)
Thanks everyone in advance!

Symfony 4 Custom config yaml file for Bundle

I'm trying to convert a bundle to symfony 4 and need to update my ancient parameters.yml to the modern symfony 4 way of life. Basicall the bundle itself - shared across multiple apps - should have a configurable file under /config/packages/.
However I receive this error:
(1/1) InvalidArgumentException
There is no extension able to load the configuration for "ptmr" (in /var/www/html/ptmr/pws_ptmrio_dev/PtmrBundle/DependencyInjection/../../config/packages/ptmr.yaml). Looked for namespace "ptmr", found none
/PtmrBundle/DependencyInjection/PtmrExtension.php
<?php
namespace PtmrBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class PtmrExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration(true);
$config = $this->processConfiguration($configuration, $configs);
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../../config/packages')
);
$loader->load('ptmr.yaml');
}
}
/PtmrBundle/DependencyInjection/Configuration.php
<?php
namespace PtmrBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
private $debug;
public function __construct($debug = true)
{
$this->debug = (bool) $debug;
}
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('ptmr');
$treeBuilder->getRootNode()
->children()
->arrayNode('twitter')
->children()
->integerNode('client_id')->end()
->scalarNode('client_secret')->end()
->end()
->end() // twitter
->end()
;
return $treeBuilder;
}
}
/config/packages/ptmr.yaml
ptmr:
twitter:
client_id: 123
client_secret: your_secret
--
Note:
The Bundle itself works.
I added this line to psr-4 in composer.json:
"PtmrBundle\\": "PtmrBundle/"
This lines to config/routes/annotations.yml
ptmr_bundle:
resource: ../PtmrBundle/Controller/
type: annotation
These lines to config/services.yaml
services:
...
PtmrBundle\:
resource: '../PtmrBundle/*'
exclude: '../PtmrBundle/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
...
PtmrBundle\Controller\:
resource: '../PtmrBundle/Controller'
tags: ['controller.service_arguments']
And of course PtmrBundle/PtmrBundle.php
<?php
namespace PtmrBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class PtmrBundle extends Bundle
{
}
I'm following these instructions and I reaaaly do not see any errors. What am I missing? Symfony 4.2.
I found the answer after all. To make your own config/bundle.yaml parameters file, simply do:
Step 1: Create a file in your bundle DependencyInjection/{BundleNameWithoutBundle}Extension.php, e.g. for MyBundle > MyExtension.php
<?php
namespace MyBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class MyExtension extends \Symfony\Component\DependencyInjection\Extension\Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('my', $config);
}
}
Also see How to Load Service Configuration inside a Bundle
Step 2: Make a configuration file that provides a schema for your .yaml file DependencyInjection/Configuration.php
<?php
namespace MyBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('my');
$treeBuilder->getRootNode()
->children()
->variableNode('project_name')->end()
->end()
;
return $treeBuilder;
}
}
Also see How to Create Friendly Configuration for a Bundle
Step 3: Mirror the Configuration.php in your config/my.yaml
my:
project_name: "My Name"
Now the parameter my is available (set in MyExtension Class). Simply get it in your Controller like:
class IndexController extends AbstractController
{
public function indexAction(ParameterBagInterface $parameterBag)
{
...
dump($parameterBag->get('ptmr'));
return $this->render('index.html.twig');
}
}
Note: Most bundles go further and manipulate the bundles config xml files, which is out of scope for a simple question like this. A simple example on how to do this is the KnpPaginatorBundle which is not overly complicated to understand for settings parameters.
Personal Note: It seems to me, the Symfony docs are overly complicated and should provide a simple example. You got to know a lot of nomenclature and it's hard to learn it, especially compared to other well documented symfony chapters.
You are attempting to load configuration for your bundle before Symfony is aware of your bundle. Your bundle class must be loaded and configured before you can parse configuration under the ptmr property.
Typically your bundle would load __DIR__.'/../Resources/config/services.yaml which contains service and parameters definitions. Outside of your bundle, in your application config/ dir, you would then load configuration under the ptmr property.

register custom config namespace for Symfony MicroKernel

I have an app with a MicroKernel and would like to use Symfony's TreeBuilder with a custom config because the app will be quite generic and I would like to have as much as possible configured externally. My problem is, I can't get my custom namespace registered for symfony:
There is no extension able to load the configuration for "research"
(in /var/www/html/src/app/config/config.yml). Looked for
namespace "research", found "framework", "twig",
"sensio_framework_extra", "web_profiler"
I have this Configuration class:
<?php
namespace App\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('research');
// #formatter:off
$rootNode
->children()
->arrayNode('questions')
->children()
->integerNode('client_id')->end()
->scalarNode('client_secret')->end()
->end()
->end()// twitter
->end();
// #formatter:on
return $treeBuilder;
}
}
and my AppExtension:
<?php
namespace App\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
class AppExtension extends ConfigurableExtension
{
// note that this method is called loadInternal and not load
protected function loadInternal(array $mergedConfig, ContainerBuilder $container)
{
dump($mergedConfig);
}
public function getAlias()
{
return 'research';
}
}
In my AppKernel class, I'm initializing the extension like this and as a result I also get an output from the dump($mergedConfig) call in my AppExtension from my original research.yml
<?php
namespace App;
use App\DependencyInjection\AppExtension;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
$loader = require __DIR__ . '/../vendor/autoload.php';
// auto-load annotations
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
/**
* Class AppKernel
*/
class AppKernel extends Kernel
{
use MicroKernelTrait;
...
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
{
$researchConfig = Yaml::parse(file_get_contents(__DIR__ . '/config/research.yml'));
$appExtension = new AppExtension();
$appExtension->load(researchConfig, $c);
$loader->load(__DIR__ . '/config/config.yml');
}
...
}
nevertheless, when my config.yml looks like this, I get the error mentioned above:
framework:
secret: S0ME_SUPER_SECRET
profiler: { only_exceptions: false }
research:
questions:
client_id: 2342323423
clent_secret: mysecret
so what do I need to do, to actually register my namespace research so I can properly overwrite it? Or does it have to an external vendor bundle?
You get the error because you dont register a bundle and so symfony doesnt know the bundle config namespace.
I cant say how you could fix this with your approach but i would suggest to make a AppBundle instead of the App and register AppBundle in public function registerBundles().
See f.E. in some boilerplates like https://github.com/ikoene/symfony-micro.
Alternativly you could try to set up your project with the new symfony Flex.
This already uses MicroKernel, is bundleless and also supports latest stable symfony version (sf3.3).
https://symfony.com/doc/current/setup/flex.html#using-symfony-flex-in-new-applications
So no need to build something off the track yourself.
Since Symfony does not recommend the usage of Bundles anymore (), to create a Bundle is NOT the way to solve this.
#see [https://symfony.com/doc/current/bundles.html][1]
In Symfony versions prior to 4.0, it was recommended to organize your own application code using bundles. This is no longer recommended and bundles should only be used to share code and features between multiple applications.
[1]: Symfony Bundle Documentation

Symfony container has no extensions when loading my bundle

I have a bundle which was working quite well for some time. However, I had to add some custom configuration params to it, so I wrote some lines in the bundle's config.yml, something like this:
# ...
acme_my_bundle:
special_params: ['param_1', 'param_2']
The configuration is defined in the bundle's Configuration class:
namespace ACME\MyBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface {
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder() {
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('acme_my_bundle');
$rootNode
->children()
->arrayNode('special_params')
->end()
->end();
return $treeBuilder;
}
}
The bundle is properly registered in AppKernel.php:
public function registerBundles() {
$bundles = array(
// ...
new ACME\MyBundle(),
// ...
);
// ...
return $bundles;
}
However, when I try to use my app, I get an error:
There is no extension able to load the configuration for "acme_my_bundle" (in (path_to_bundle)/MyBundle/DependencyInjection/../Resources/config/config.yml). Looked for namespace "acme_my_bundle", found none
I looked it up, but most results found were unsatisfactory - I eliminated the problems that came up during searching:
improper configuration structure
bundle not registered in the app kernel
config root node name different than the one returned from ACMEMyBundleExtension::getAlias()
I tried debugging the cause of the exception being thrown and discovered that when the YAML file loader tries to validate my config file, the container has no extensions:
var_dump($container->getExtensions()); // prints empty array - array(0) { }
It causes the validation to fail and the none part of the message to be displayed - there a no available extensions.
I tried debugging $this->extensions in ContainerBuilder::hasExtension() and for some reason the list is complete when the method is launched for the vendor bundles, but is empty for my bundle. It looks like that something in my bundle is still defined or registered incorrectly.
I changed the names off classes, etc. not to expose company code, excuse me for that if it causes confusion.
EDIT: I didn't explicitly mention it, but the Extension class is defined and the exception occurs when it is loaded - just as I have written above:
when the YAML file loader tries to validate my config file
To be more clear, here is my Extension class:
namespace ACME\MyBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class ACMEMyBundleExtension extends Extension {
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container) {
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
// The exception is thrown here
$loader->load('config.yml');
}
}
Check your configuration reader in ACME\MyBundle\DependencyInjection\Configuration for $rootNode = $treeBuilder->root('BUNDLE_CONFIG_KEY');.
BUNDLE_CONFIG_KEY should be:
valid (same in ACME\MyBundle\DependencyInjection\Configuration and your config.yml
unique for the app
Also please check you're defining bundle configuration in correct way - it should be added to app/config/*.yml (one of global config files). Maybe you have added acme_my_bundle config in other custom bundle config files?
You have missed bundle extension class (ACME\MyBundle\DependencyInjection\ACMEMyExtension) as explained here http://symfony.com/doc/current/cookbook/bundles/extension.html. Cookbook entry for bundle configuration is here. Key in config.yml should be named only as acme_my.
Creating the Configuration class alone is not enough. You need to register a dependency injection extension and use the Configuration class in there.
Read more about in the How to Create Friendly Configuration for a Bundle cookbook:
[The Configuration] class can now be used in your load() method to merge configurations and force validation (e.g. if an additional option was passed, an exception will be thrown)
namespace Acme\MyBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeMyBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// ...
}
}
Naming your extension class according to the convention will make it's automatically loaded. More on creating DIC extension classes in Creating an Extension Class. You can also enable the extension manually, see Manually registering an extension class.

Categories