I had many helper function -Grouped In Classes - for ( formatting Strings And Dates , URL Helpers ) that i want to use and share in several bundles , i need to know best practice about where i can put those helpers functions to be shared between bundles.
What came to my mind is to create a helper bundle and use this bundle in the other bundle that i have in my project or use the vendor helper.
So how i can do this and what is the best practice for Creating Shared Helper to be used in multiple bundles.
Please if there is any references i can look at please share it with me.
Thank you in advance.
Best practice would be to create a PHP library containing those classes. If you really need Symfony integration (eg. DIC configuration), then create bundle that depends on this library.
Every bundle that uses your bundle must list it among it's dependences in composer.json. So it will be installed autocratically every time you install bundle that depends on it.
There are plenty of great examples of libraries out there, that can be imported using composer and used even if they aren't bundles per se, take a look at Doctrine\Common for example.
Regardless, you can also create the bundle as you would any other bundle in Symfony, and structure the code as you see fit. You will notice with many of Doctrine's bundles will make use of the shared library Doctrine\Common.
If You have universal classes it should be grouped in one bundle ("Helper bundle" as You said) and if it is possible in Your case classes should be defined as services.
If You are using this bundle in more than one project and You want to upgrade that in the future, You should think about moving this bundle to separate repo and define it as a "standalone" bundle (so You can include that in your projects by composer and vendors directory.
I think Best practice is create helper Bundle and create service in helper bundle
Then you can use service in several bundle.
dummy example: in your service Helper.php
namespace HelperBundle\Services;
class Helper{
protected $url;
public function __construct(){
}
}
in ProfileTreeUserExtension.php in Dependency Injection folder
confirm that services configuration file which loaded is sevices.yml
namespace HelperBundle\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 ProfileTreeLayoutExtension 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');
}
}
in service.yml
services:
helper.service:
class: HelperBundle\Services\Helper
Then you can call HelperService in several bundles Just
$helper = $this->container->get('helper.service');
you can also extends Helper class in another service
use HelperBundle\Services\Helper;
class AnotherService extends Helper{}
There good article about Service Container and Dependency Injection
Related
I search for a way to change the configuration of the Symfony twig bundle in order to register additional twig namespaces.
I tripped over prepend extension but this would just solve the problem half way. When I write code like this:
public function prepend(ContainerBuilder $container)
{
$bundles = $container->getParameter('kernel.bundles');
if (isset($bundles['TwigBundle'])) {
$config = $container->getExtensionConfig('twig')[0];
$paths = ['/path/to/cms' => 'cms'];
if (array_key_exists('path', $config)) {
$paths = array_merge($config['paths'], $paths);
}
$config['paths'] = $paths;
$container->prependExtensionConfig('twig', $config);
}
}
This would mean that no other bundle could ever change the twig configuration again:
How to add twig namespaces from inside a bundle without obstructing a way for other bundles to do the same?
You can do this by creating a Compiler Pass within your Bundle:
class CustomCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if ($container->hasDefinition('twig.loader.native_filesystem')){
$bundleDirectory = \dirname(__DIR__,2);
$twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.native_filesystem');
$twigFilesystemLoaderDefinition->addMethodCall('addPath', [$bundleDirectory.'/templates/admin', 'admin']);
}
}
}
Twig namespaces are an application concern, not a bundle concern.
Registering new namespaces from within the bundle could clash with already defined namespaces by the application that consumes the bundle.
Furthermore, it's not necessary, because Symfony already auto-crates a namespace for each bundle:
If you install packages/bundles in your application, they may include their own Twig templates (in the Resources/views/ directory of each bundle). To avoid messing with your own templates, Symfony adds bundle templates under an automatic namespace created after the bundle name.
For example, the templates of a bundle called AcmeFooBundle are available under the AcmeFoo namespace. If this bundle includes the template <your-project>/vendor/acmefoo-bundle/Resources/views/user/profile.html.twig, you can refer to it as #AcmeFoo/user/profile.html.twig.
You shouldn't mess with that configuration. Leave it up to the application developer or the framework to deal with it.
I would like to use the AlterPHP extension as well as the Wandi extension with EasyAdminBundle.
But we face some issue configuration both of them at the same time.
We used to have this config file when using only AlterPhp :
#routes/easy_admin.yaml
easy_admin_bundle:
resource: '#EasyAdminExtensionBundle/Controller/EasyAdminController.php'
prefix: /admin
type: annotation
And it was fine when we only used this bundle. However, now we want to use this bundle as well as the one quoted previously but it also needs to replace the easyadmin controller by the one from the new bundle.
So both extension wants to do the same thing and both extend the BaseAdminController from EasyAdmin.
What would be the best way to use both in the same project ?
I found a solution by making a custom controller that extends the AdminController from Wandi and copying the methods from the AdminController from Alterphp inside the custom controller. However, it seems like an odd solution to this problem.
I decided to contact both AlterPHP and Wandi on github and send a pull request on their extensions to use trait in their controller to make it easier to use multiple extensions.
So both of them answered to me :
Wandi reviewed my PR and merged it to master. It is now available in release 2.0.2.
AlterPHP reviewed my PR and merged it to master. It is now available in release 3.0.1
So with those changes it is way easier to use both extensions (and similar EasyAdminExtension) by using those new traits :
use Wandi\EasyAdminPlusBundle\Controller\AdminController as WandiController;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
use Wandi\EasyAdminPlusBundle\Controller\AdminControllerTrait as WandiTrait;
use AlterPHP\EasyAdminExtensionBundle\Controller\AdminExtensionControllerTrait as AlterPHPTrait;
class CustomAdminController extends EasyAdminController
{
use AlterPHPTrait, WandiTrait;
//You may have to solve conflict between those traits
}
You may have multiple problems such as services not known by the controller or methods defined multiple time.
I just had to redefine getSubscribedServices in my controller to add those used by AlterPHP and Wandi, as well as resolving a conflict with the method isActionAllowed defined in both traits.
use AlterPHP\EasyAdminExtensionBundle\Security\AdminAuthorizationChecker;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
use Wandi\EasyAdminPlusBundle\Controller\AdminControllerTrait as WandiTrait;
use AlterPHP\EasyAdminExtensionBundle\Controller\AdminExtensionControllerTrait as AlterPHPTrait;
use Wandi\EasyAdminPlusBundle\Exporter\Configuration\NormalizerConfigPass;
use Wandi\EasyAdminPlusBundle\Exporter\Configuration\PropertyConfigPass;
use Wandi\EasyAdminPlusBundle\Exporter\Configuration\TemplateConfigPass;
class CustomAdminController extends EasyAdminController
{
use AlterPHPTrait,WandiTrait { AlterPHPTrait::isActionAllowed insteadof WandiTrait; }
//It is important to set the subscribed services from the trait because they cannot use them otherwise.
public static function getSubscribedServices(): array
{
return \array_merge(parent::getSubscribedServices(), [
AdminAuthorizationChecker::class, //This one is for AlterPHP and those below for Wandi
'wandi.easy_admin_plus.exporter.configuration.normalizer_config_pass' => NormalizerConfigPass::class,
'wandi.easy_admin_plus.exporter.configuration.property_config_pass' => PropertyConfigPass::class,
'wandi.easy_admin_plus.exporter.configuration.template_config_pass' => TemplateConfigPass::class,
]);
}
}
I had to modify my services.yaml to be able to redefine getSubscribedServices for Wandi.
#services.yaml
services:
#...
Wandi\EasyAdminPlusBundle\Exporter\Configuration\NormalizerConfigPass: '#wandi.easy_admin_plus.exporter.configuration.normalizer_config_pass'
Wandi\EasyAdminPlusBundle\Exporter\Configuration\PropertyConfigPass: '#wandi.easy_admin_plus.exporter.configuration.property_config_pass'
Wandi\EasyAdminPlusBundle\Exporter\Configuration\TemplateConfigPass: '#wandi.easy_admin_plus.exporter.configuration.template_config_pass'
I have a generic PHP repo which contains a number of Doctrine entities:
src
Entity
Foo.php
Bar.php
I've created a shared Symfony bundle which requires this repo in its composer.json:
"require": {
"myorg/entities": "dev-master"
},
How can I register the entities in this Symfony bundle, so that they're immediately made available to any project which includes it? I'd like to avoid the parent project having to explicitly list every entity in its orm config.
In my opinion better to register Doctrine ORM mappings pass. So your entities will be registered on compile.
namespace My\AppBundle;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MyAppBundle extends Bundle
{
/**
* #param ContainerBuilder $container
*/
public function build(ContainerBuilder $container)
{
$namespaces = ['My\AppBundle\Entity'];
$directories = [__DIR__ . '/Entity'];
$container->addCompilerPass(DoctrineOrmMappingsPass::createAnnotationMappingDriver($namespaces, $directories));
}
}
just use the #Repository annotation
for example:
#ORM\Entity(repositoryClass="myorg\entities\Repository\FooRepository")
I have created several modular applications, which shared Bundles. One of these bundles responsible for handling all entity/repository related logic (lets call it MyBundle).
This MyBundle contains several custom hydrators. How can I register these hydrators in the bundle given that the doctrine configuration is contained in the specific applications and I don't wish to have to register the bundle hydrators in their config.yml files?
I've tried creating a CompilerPass in my bundle, then calling the addCustomHydrationMode on the doctrine orm configuration service but this doesn't work.
I also tried creating a 'HydrationManager' class, injecting the entity manager, then calling an addHydrator method on the manager in the compiler pass which in turn calls getConfiguration()->addCustomHydrationMode(...) but this also did not work
I was looking for a similar solution, and stumbled upon this example:
https://github.com/vivait/user-bundle/blob/master/src/Vivait/UserBundle/DependencyInjection/DoctrineCompilerPass.php
<?php
namespace Vivait\UserBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class DoctrineCompilerPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*
* #param ContainerBuilder $container
*
* #api
*/
public function process(ContainerBuilder $container)
{
$ormConfigDef = $container->getDefinition('doctrine.orm.configuration');
$ormConfigDef->addMethodCall(
'addCustomHydrationMode',
['UserCustomerHydrator', 'Vivait\UserBundle\Adapter\Hydrator\CustomerHydrator']
);
}
}
I have the bundle which is called the Linked Bundle. Now I have put all the mix stuff in there which is used across all bundles.
But I don't have any entity called Linked.
I want to create LinkedRepository so that i can have my all common function there. But how will i get that repository in other bundles. I mean how to call this
$repository = $em->getRepository('LinkedBundle:"*What should I write here*"');
You cannot have a separate repository class. A repository class is linked to an entity, so you cannot have a "standalone" repository but I can see two options:
Subclass EntityRepository and name it LinkedRepository, here you can add your common methods. All your custom Repository class will have to subclass your LinkedRepository class. If you want that common functionality in all your Entity's Repository instances but you don't need a custom Repository class you can declare the LinkedRepository class as the Entity's repositoryClass #ORM\Entity(repositoryClass="Acme\LinkedBundle\Repository\LinkedRepository") assuming you have your Repository classes in the Repository folder inside your Bundle and replace Acme with your company name.
Create a service and add your common functionality in there.
I guess the first one is easier.
i think this it is not possible as you intend to to it. But I would recommend using a Service Container instead of a Repository. In this Service Container you can use different repositories, which you need to use for these global tasks. The Service Container is also accessible in every controller etc..
Here is the Documentatino for it: http://symfony.com/doc/current/book/service_container.html
I don't think that you need the whole injection part. Just define an Service:
services:
linked_service:
class: Acme\LinkedBundleBundle\Service\LinkedService
And then get the service in your controller via
public function indexAction()
{
$service = $this->get('linked_service');
}
Hope this works.