Symfony Bundle not creating bundle-configuration yaml file in project - symfony

A so far working bundle now needs its own configuration file inside the projects using the bundle, to manage bundle settings individually.
However, no matter which approach I use (the old one before Symfony 6.1 nor the new one extending AbstractBundle) there is - at no time - any new .yaml-File created inside the projects ./config/packages/ directory.
This is my code (the old style, prior to Symfony 6.1, extending Bundle):
Bundle Class
mycorpforms/src/MyCorpFormsBundle.php
<?php
namespace MyCorp\FormsBundle;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MyCorpFormsBundle extends Bundle
{
// empty
}
Configuration
mycorpforms/src/DependencyInjection/Configuration.php
<?php
namespace MyCorp\FormsBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('mycorp_forms');
$treeBuilder->getRootNode()
->children()
->booleanNode('favorite_submenu_enabled')->defaultFalse()->end()
->end()
;
return $treeBuilder;
}
}
Extension
mycorpforms/src/DependencyInjection/MyCorpFormsExtension.php
<?php
namespace MyCorp\FormsBundle\DependencyInjection;
use Knp\Bundle\SnappyBundle\DependencyInjection\Configuration;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class MyCorpFormsExtension extends Extension
{
public function load(array $configs, ContainerBuilder $containerBuilder)
{
$loader = new YamlFileLoader(
$containerBuilder,
new FileLocator(__DIR__.'/../../config/packages')
);
$loader->load('mycorp_forms.yaml');
$configuration = new Configuration();
$processor = new Processor();
$config = $processor->processConfiguration($configuration, $configs);
$containerBuilder->setParameter('mycorp_forms.favorite_submenu_enabled', $config['favorite_submenu_enabled']);
}
}
Yaml
Additionally I added the desired mycorp_forms.yaml inside the bundles ./config/packages/ dir.
This is the actual file required in the projects:
mycorpforms/config/packages/mycorp_forms.yaml
mycorp_forms:
# Enable Favorite-Sub-Menu (Requires Knp-Snappy-Bundle !)
favorite_submenu_enabled: false
The bundle installs flawlessly in any of my projects, however no mycorp_forms.yaml file is created. Obviously this requires symfony/flex which is so far required by the bundle itself.
Q: What do I miss here?
Q: How can this yaml-file automatically be added when the bundle is installed?
I read the documentation up and down numerous times, but to be honest, I get more confused every time.
Thank you very much for any help or explanation!

This requires Symfony Flex
To have a .yaml config file being generated/updated automatically, there is 2 possible ways:
public bundle: register it in the public recipe repository
private bundle: set up your own private flex recipe repository

Related

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.

Symfony Bundle that needs a file that differs per project

I've written a small newsletter bundle for the multiple sites I host/develop for. The newsletter fetches the recipients from a newsletter source that differs from project to project. Could be a csv file, could be a database, ...
So in my Controller I thought about writing a NewsletterQueueImportModel() which is being called upon hitting the button "import".
...
$import = new NewsletterQueueImportModel();
$subscribers = $import->getSubscribers($this->getDoctrine());
...
However this file is still delivered with my bundle and in the vendor folder. So I need to change this file on a per project basis.
Override the file, but how? I do not think this is possible.
Remove the file from the newsletter bundle itself and refer to AppBundle/NewsletterQueueImportModel (e.g. use AppBundle instead of use NewsletterBundle - downsides: all projects need to be named AppBundle and I feel it is poor design
I thought about registering a service or something like that, but I really don't know the beste way to do. So what is the best way to create a file on which a bundle depends but differs from project to project?
Well, I have been doing something similar but with views.
So I had 2 sites running on one bundle - in one site I needed to see some sections which i don't need in other site.
I managed to do that with configuration.
1) In each of your site - app/config/config.yml you can define parameters. In my case it was something like
reviews_admin:
views:
table_favorite_block: true
table_brand_view: true
table_image_view: true
2) Then in bundle you must create folder named DependencyInjection with 2 files in it. Configuration and your bundle extension
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('reviews_admin', 'array');
$rootNode
->children()
->arrayNode('views')
->children()
->booleanNode('table_favorite_block')->defaultTrue()->end()
->booleanNode('table_brand_view')->defaultTrue()->end()
->booleanNode('table_image_view')->defaultTrue()->end()
->end()
->end()
->end();
return $treeBuilder;
}
}
Extension
class ReviewsAdminExtension 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'));
$loader->load('services.yml');
$container->setParameter('reviews_admin_view', $config['views']);
}
}
I'm not sure if this will suite your situation, but for me it seems like the most convenient way how to manage things in bundles which depends on projects.
Also you can try to make one base class in bundle (which contains things that will be same for all projects (for imports))
And then extend it in site side ?

Symfony bundle config parameters not available in listener?

I have a bundle that has a listener which I have configured:
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder ()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('mybundle_name');
$rootNode
->children()
->scalarNode('name')->defaultValue('value')
->end()
;
return $treeBuilder;
}
}
I also have a listener which has a few services injected into, primarily doctrine and the container parameters:
services:
app.router_subscriber:
class: MyBundle\EventSubscriber\RequestSubscriber
calls:
- [setEntityManager, ['#doctrine.orm.entity_manager']]
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
When I dump the $this->container I can see the parameters except my own defined above.
When I run
bin/console config:dump-reference MyBundle
I do see what I am expecting
What am I missing to have my bundle parameters get merged into the application parameters? I am seeing third party bundles listed but not my own bundle. I followed the docs as verbatim as I could so the conventions have been followed as far as I am aware...
EDIT | I haven't created a bundle config.yml file - I assumed the Configuraiton object did that for me - setting the schema and default values - which could be overridden by application configs (if desired). Do I need to specifcy a bundle config.yml and import into application something like this (Merge config files in symfony2)?
Ideas?
I wrote a couple blog posts showing how you can set bundle configuration defaults using YAML files, and a follow-up on how to automatically set bundle configuration values as container parameters. This was for Symfony2 and written in 2014, and the particular section of the Symfony documentation I link to disappeared from Symfony 2.3 onward, but the same concept still applies.
The main takeaway from those posts is that you can set your configuration values as container parameters in your bundle's Extension class via the load() method manually like so:
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter($this->getAlias().'.name', $config['name']);
}
Notice that you can call $this->getAlias() to get the bundle's root name (mybundle_name). Using the above call you would then have a parameter defined as mybundle_name.name which you could then override in your application's config.yml if need be.

symfony 3.1 Check if a bundle is installed

I'm developing a bundle who has a dependency on another one.
In order to handle the case that the base bundle has not been installed I'll like to perform a "bundle_exists()" function inside a controller.
The question is: How can I have a list of installed bundles or How can I check for the name (eventually also the version) of a bundle.
Thanks.
In addition to #Rooneyl's answer:
The best place to do such a check is inside your DI extension (e.g. AcmeDemoExtension). This is executed once the container is build and dumped to cache. There is no need to check such thing on each request (the container doesn't change while it's cached anyway), it'll only slow down your cache.
// ...
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$bundles = $container->getParameter('bundles');
if (!isset($bundles['YourDependentBundle'])) {
throw new \InvalidArgumentException(
'The bundle ... needs to be registered in order to use AcmeDemoBundle.'
);
}
}
}
Your class needs to have access to the container object (either by extending or DI).
Then you can do;
$this->container->getParameter('kernel.bundles');
This will give you a list of bundles installed.
Update;
If you are in a controller that extends the Symfony\Bundle\FrameworkBundle\Controller\Controller or in a command class that extends Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand, you can just get the parameter.
$this->getParameter('kernel.bundles').
Else #Wouter J's answer is your best answer.
You can get a list of all Bundles from the Kernel like this:
public function indexAction ()
{
$arrBundles = $this->get("kernel")->getBundles();
if (!array_key_exists("MyBundle", $arrBundles))
{
// bundle not found
}
}
From Andrey at this question: How do I get a list of bundles in symfony2?
If you want to call a non static method of registered bundle object (not class) then you can do the following:
$kernel = $this->container->get('kernel');
$bundles = $kernel->getBundles();
$bundles['YourBundleName']->someMethod();
Where 'YourBundleName' is the name of your bundle, which you can get by calling from console:
php app/console config:dump-reference

Service in symfony2 - how service file should look like?

I am trying to create service in symfony2 which will verify if session contains certain information and if not redirect the user to another controller. I want this piece of code to work as a service as I will be using it in many controllers.
I have problem as manual on Symfony2 book does not provide information how service file should look like. Should it be a normal php class?
Please find below dump of my files with information on error that I receive.
In \AppBundle\Services I create file my_isbookchosencheck.php containing:
<?php
namespace AppBundle\my_isbookchosencheck;
class my_isbookchosencheck
{
public function __construct();
{
$session = new Session();
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
My service.yml:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
My conntroller file:
/**
* This code is aimed at checking if the book is choseen and therefore whether any further works may be carried out
*/
$checker = $this->get('my_isbookchosencheck');
Error:
FileLoaderLoadException in FileLoader.php line 125: There is no extension able to load the configuration for "my_isbookchosencheck" (in C:/wamp/www/symfony_learn/app/config\services.yml). Looked for namespace "my_isbookchosencheck", found "framework", "security", "twig", "monolog", "swiftmailer", "assetic", "doctrine", "sensio_framework_extra", "fos_user", "knp_paginator", "genemu_form", "debug", "acme_demo", "web_profiler", "sensio_distribution" in C:/wamp/www/symfony_learn/app/config\services.yml (which is being imported from "C:/wamp/www/symfony_learn/app/config\config.yml").
There are few mistakes that you made, which I am going to explain in short, and I will give you an example of the service you want to create.
You created your service in AppBundle\Services, yet your namespace is registered differently - namespace AppBundle\Services\my_isbookchosencheck;. It should be namespace AppBundle\Services;. I would also advise you to use singular names when creating directories - in this case Service would be better, instead of Services.
You're using your __constructor directly to apply some logic and return the result of it. Better way would be to create a custom method, which could be accessed when necessary.
You're creating new instance of Session which means that you wont be able to access anything that was previously added and stored in session. The right way here, would be to inject RequestStack which holds the current Request and get the session from there.
I believe you also registered your service wrong. In your services.yml file, it should be under services: option. This is why you got the error you pasted.
So, let's see how your service should like.
services.yml
services:
book_service:
class: AppBundle\Service\BookService
arguments:
- #request_stack
- #router
BookService.php
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class BookService {
/* #var $request Request */
private $request;
/* #var $router RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router) {
$this->request = $requestStack->getCurrentRequest();
$this->router = $router;
}
public function isBookChoosen() {
$session = $this->request->getSession();
// Now you can access session the proper way.
// If anything was added in session from your controller
// you can access it here as well.
// Apply your logic here and use $this->router->generate()
}
}
Now in your controller you can simply use it like this:
$this->get('book_service')->isBookChoosen()
Well this is a short example, but I hope you got the idea.
try
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
in your services.yml, and check that you use the correct namespaces.
Your Class is fine and it should work, however may i suggest that you use
symfony2 session service instead of creating the session object yourself, you can pass it as a constructor argument:
<?php
// namespace edited
namespace AppBundle\Services;
use Symfony\Component\HttpFoundation\Session\Session;
class my_isbookchosencheck
{
public function __construct(Session $session);
{
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
and then edit your services.yml accordingly, so the service container will inject the session object:
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
arguments: [#session]
Also check out his question on so:
How do you access a users session from a service in Symfony2?
Services are just regular PHP classes, nothing special. But you must register it in order to be recognized by the system. Here are the steps how you do it,
Create a regular PHP class (you can inject other services if it requires)
namespace Acme\DemoBundle\Service;
class MyService
{
private $session;
public function _construct(SessionInterface $session /* here we're injecting the session service which implements the SessionInterface */)
{
$this->session = $session;
}
// other methods go here, which holds the business logic of this class
}
ok, we created a class, we need to register it to be able to use it by service container, here how you do it:
the simplest way is to put it into config.yml file, like this:
services:
my_service:
class: Acme\DemoBundle\Service\MyService
arguments:
- #session
or, another way, is to create a file (e.g. services.yml, may be in config folder), and import it inside the config.yml file (the content of the file is the same as the first way):
imports:
- { resource: services.yml }
or, you can create a services.yml(the content of the file is the same as the first way) file inside you bundle's Resources folder, specify it under the load method of your Extension class (under the DependencyInjection folder), (this way requires some special directory and file structure, read about it in the doc):
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources'));
$loader->load('services.yml');
}
}
In you case, you're not registering your service, the service container just couldn't find it. Register it by one of the above ways.

Resources