Symfony Bundle that needs a file that differs per project - symfony

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 ?

Related

Symfony Bundle not creating bundle-configuration yaml file in project

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

upgrade symfony3.4 with bundles extension to symfony4 flex

I have a project in symfony 3.4 then I tried to upgrade to symfony 4 /flex.
the problem is in my code there a 3 bundles TBAdminBundle, TBPlatformBundle and TBSecurityBundle.
Also for each bundle there Extension class exemple.
class TBPlatformExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new PlatformConfiguration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
if (!in_array(strtolower($config['db_driver']), array('custom', 'mongodb', 'orm'))) {
throw new \InvalidArgumentException(sprintf('Invalid db driver "%s".', $config['db_driver']));
}
if ('custom' !== $config['db_driver']) {
$loader->load(sprintf('%s.yml', $config['db_driver']));
$def = new Definition('Doctrine\ORM\EntityManager', array('%tb_notification.model_manager_name%'));
$def->setPublic(false);
if (method_exists($def, 'setFactory')) {
$def->setFactory(array(new Reference('doctrine'), 'getManager'));
} else {
// To be removed when dependency on Symfony DependencyInjection is bumped to 2.6
$def->setFactoryService('doctrine');
$def->setFactoryMethod('getManager');
}
$container->setDefinition('tb_notification.entity_manager', $def);
}
foreach (array('form', 'command', 'events') as $basename) {
$loader->load(sprintf('%s.yml', $basename));
}
$container->setParameter('tb_notification.model_manager_name', $config['model_manager_name']);
$container->setParameter('tb_notification.form.notification.type', $config['form']['notification']['type']);
$container->setParameter('tb_notification.form.notification.name', $config['form']['notification']['name']);
$container->setParameter('tb_trip.form.trip.type', $config['form']['trip']['type']);
$container->setParameter('tb_trip.form.trip.name', $config['form']['trip']['name']);
$container->setParameter(
'tb_notification.form.delete_notification.type',
$config['form']['delete_notification']['type']
);
$container->setParameter(
'tb_notification.form.delete_notification.name',
$config['form']['delete_notification']['name']
);
$container->setParameter('tb_trip.form.trip.update_trip.type', $config['form']['update_trip']['type']);
$container->setParameter('tb_trip.form.trip.update_trip.name', $config['form']['update_trip']['name']);
$container->setParameter('tb_action.form.action.type', $config['form']['action']['type']);
$container->setParameter('tb_action.form.action.name', $config['form']['action']['name']);
$container->setParameter('tb_place.form.place.type', $config['form']['place']['type']);
$container->setParameter('tb_place.form.place.name', $config['form']['place']['name']);
$container->setParameter('tb_action_privacy.form.action.type', $config['form']['privacy']['type']);
$container->setParameter('tb_action_privacy.form.action.name', $config['form']['privacy']['name']);
$container->setParameter('tb_notification.model.notification.class', $config['class']['model']['notification']);
$container->setParameter('tb_trip.model.trip.class', $config['class']['model']['trip']);
$container->setParameter('tb_like.model.like.class', $config['class']['model']['like']);
$container->setParameter('tb_helpful.model.helpful.class', $config['class']['model']['helpful']);
$container->setParameter('tb_action.model.action.class', $config['class']['model']['action']);
$container->setParameter('tb_budget.model.class', $config['class']['model']['budget']);
$container->setParameter('tb_action.model.action_privacy_policy.class', $config['class']['model']['privacy']);
$container->setParameter('tb_rating.model.rating.class', $config['class']['model']['vote']);
$container->setParameter('tb_place.model.place.class', $config['class']['model']['place']);
$container->setParameter('tb_destination.model.destination.class', $config['class']['model']['destination']);
// parameters for hydrating object with doctrine
$container->setParameter('tb_action.hydrate.action', $config['hydrate']['action']);
$container->setParameter('tb_google.key', $config['google']['key']);
$container->setParameter('compare_text.default_percent', $config['compare']['text']);
$container->setAlias('tb_trip.manager.trip', $config['service']['manager']['trip']);
$container->setAlias('tb_like.manager.post', $config['service']['manager']['like']);
$container->setAlias('tb_helpful.manager.post', $config['service']['manager']['helpful']);
$container->setAlias('tb_budget_manager', $config['service']['manager']['budget']);
$container->setAlias('tb_place_manager', $config['service']['manager']['place']);
$container->setAlias('tb_destination_manager', $config['service']['manager']['destination']);
$container->setAlias('tb_compare_string', 'similar_text.manager');
$container->setAlias('foursquare.manager', 'foursquare.manager.default');
$container->setAlias('tb_action.form_factory', $config['service']['form_factory']['action']);
$container->setAlias('tb_place.form_factory', $config['service']['form_factory']['place']);
$container->setAlias('tb_action_privacy.form_factory', $config['service']['form_factory']['privacy']);
$container->setAlias(
'tb_notification.form_factory.notification',
$config['service']['form_factory']['notification']
);
$container->setAlias('tb_trip.form_factory', $config['service']['form_factory']['trip']);
$container->setAlias(
'tb_notification.form_factory.notification_delete',
$config['service']['form_factory']['delete_notification']
);
$container->setAlias('tb_action.customer_repository', 'tb_action.customer_repository_default');
$container->setAlias('tb_notification.customer_repository', 'tb_notification.customer_repository_default');
$container->setAlias('tb_trip.customer_repository', 'tb_trip.customer_repository_default');
}
}
According to documentation
"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."
I know this it's no more possible with symfony4/flex
How can I rewrite this to match with flex configuration?
thanks
My suggestion is to migrate progressively your bundles.
1. Put the bundle in the src of your SF4 application like this :
src/
Bundle/
TBAdminBundle
TBPlatformBundle
TBSecurityBundle
Command
Controller
Entity
...
2. Use Composer PSR-4 to autoload them (composer.json)
"autoload" : {
"psr-4" : {
"App\\" : "src/",
"TBAdminBundle\\" : "src/Bundle/TBAdminBundle/",
"TBPlatformBundle\\" : "src/Bundle/TBPlatformBundle/",
"TBSecurityBundle\\" : "src/Bundle/TBSecurityBundle/",
...
3. Exclude them from App Services autoload (config/services.yaml)
App\:
resource: '../src/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../src/{Entity,Migrations,Repository,Bundle}'
4. Validate the new installation/configuration
Although SF4 is bundle less for core App application, it supports bundle ... Just check that you resolve all deprecated functions from those bundles
5. Start progressive migration of your bundles code:
As Hint :
Rework your config/parameters to use ENV, and services.yaml parameters
So you can transfer most config variables/parameters to the App level and easily share them ...
All your services definitions can be moved from Extention classes into services.yaml for simplicity and easy maintenance.
You will degrease bundles code as time go and you gain experience with the new SF4 services usability and orientations.
That note tells you that it is no longer a recommendation but it doesn't mean that you should refactor your code completely. If you already have your code packed as bundles you should only remove deprecated code. In general it is a good idea to pack your code in bundles only if it is reusable across projects.

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

Symfony2. Howto multiple config files in a bundle

I just can't figure this out.
I am writing a report builder in Symfony2.
I have a config file like this:
bundle:
sections:
Report1:
buckets:
bucket1:
...
calculations:
calculation1:
...
report:
rows:
row1:
...
For several reports, this gets to be loooong.
I've tried breaking this file into smaller files and loading them separately. This didn't work. Here's what I tried:
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('bundle_report.yml');
$loader->load('bundle_report_1.yml'); // Order is important here
$loader->load('bundle_report_2.yml');
$loader->load('bundle_report_3.yml');
$loader->load('services.yml');
}
What's the best way to do this? Is it even possible?
The error I'm getting is (exception is thrown before $loader->load()s happen):
The child node "sections" at path "bundle" must be configured
If I switch the order ( $loader->load()s first, then new Configuration()):
There is no extension able to load the configuration for "bundle"
Configuration looks like this:
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('bundle');
$rootNode
->children()
->arrayNode('sections')->isRequired()
->prototype('array')
...
Here's what I did.
Loaded it all into one 'bundle.yml' config file.
Put that file in app/config
Imported it in app/config.yml imports section
Defined the 'Configuration' class in my bundle (see above). Bundle generator created this.
Then, in my BundleReportExtension class, added these lines
// Parse our configuration. It's included in /app/config/config.yml
// Parser is Configuration class in this package.
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// Great. Parsed. Now copy to parameters for later use.
$container->setParameter('bundle.default_config', $config['default_config']);
$container->setParameter('bundle.sections', $config['sections']);
Now, I can't get the configuration as an array with:
$container->setParameter('bundle.sections');
In much searching this is the best I could come up with. If you have other suggestions, please share.
You are not supposed to load your config files from the load method.
This is made to
load the base config from Configuration class
compare it to custom config (yml files in app/config/)
and load the bundle services
All your config files must be in app/config/ to be passed as parameters to the load method. As you do it here, they are useless.
That's why you get
The child node "sections" at path "bundle" must be configured
And if you reports are evolving, maybe it's better to put this in a business logic (model/entities) and to plug an admin (eg: sonata admin).

Resources