I wrote custom Twig loader that fetch templates from database and it works in Twig "standalone" library.
Now i want to use that in Symfony2 but can't find where to change Twig loader via Symfony2 settings.
Thx in advance for any tips on that
Register your own twig loader + tell Twig_Loader_Chain to try loading with your loader at first. You can create and add as many loaders to your Twig_Loader_Chain as you want.
services:
Acme.corebundle.twig.loader.filesystem:
class: Acme\CoreBundle\Twig\Loader\Filesystem
tags:
- { name: templating.loader }
Acme.corebundle.twig_chain_loader:
class: Twig_Loader_Chain
calls:
- [ addLoader, [#Acme.corebundle.twig.loader.filesystem] ]
- [ addLoader, [#twig.loader] ]
Now you should create your loader. Twig loaders have to implement Twig_LoaderInterface.
Acme/CoreBundle/Twig/Loader/Filesystem.php
PSEUDOCODE:
namespace Acme\CoreBundle\Twig\Loader;
use Twig_LoaderInterface;
class Filesystem implements Twig_LoaderInterface {
/**
* {#inheritdoc}
*/
public function getSource($name)
{
//code...
}
/**
* {#inheritdoc}
*/
protected function findTemplate($name)
{
//code...
}
/**
* {#inheritdoc}
*/
public function isFresh($template, $time)
{
//code...
}
//...
}
Now we have defined our services and created a new loader.
Problem is that Twig doesn't know about our new Twig_Loader and still uses its own -default- "twig.loader".
To check run on CLI:
app/console container:debug twig.loader
In order to modify services outside of your own bundle you have to use CompilerPasses.
Create our own that assigns your loader service to the twig environment:
Acme/CoreBundle/DependencyInjection/Compiler/TwigFileLoaderPass.php
<?php
namespace Acme\CoreBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class TwigFileLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('twig');
$definition->addMethodCall('setLoader', array(new Reference('Acme.corebundle.twig_chain_loader')));
}
}
There is the "addMethodCall" call which does nothing more than defining a setter injection as in the service definitions. The difference is that in a compiler pass you can access every service, not only your own ones. As you can see the chain loader has been defined as the new loader for the twig environment.
To Accomplish this task you have to tell Symfony that it should use this compiler pass. Compiler passes can be added in your bundle class:
Acme/CoreBundle/AcmeCoreBundle.php
<?php
namespace Acme\CoreBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Acme\CoreBundle\DependencyInjection\Compiler\TwigFileLoaderPass;
class AcmeCoreBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new TwigFileLoaderPass());
}
}
If the corresponding file does not exist your new Twig_Loader_Filesystem throws an error and the chain loader continues with default twig loader as fallback.
Have a look at this page at GitHub. Specially <parameter key="twig.loader.class">Symfony\Bundle\TwigBundle\Loader\Loader</parameter>
You can configure this key in your config.yml
To overwrite the key in your config.yml you need to do it under services not twig as it's not support in the configuration parser at the moment (2.0.9)
twig:
cache:...
debug:...
...
services:
twig.loader:
class: Acme\CoreBundle\Twig\Loader\FilesystemLoader
Related
I am using Symfony 3.4.0, I try to load fixtures with:
php bin/console doctrine:fixtures:load
An error occurred while creating the data, what's wrong?
This command looks for all services tagged with doctrine.fixture.orm.
There is two ways to fix this problem.
First one: any class that implements ORMFixtureInterface will automatically be registered with this tag.
<?php
namespace AppBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
class LoadFixtures implements ORMFixtureInterface
{
public function load(ObjectManager $manager)
{
#your code
}
}
Second one: You need manually tag doctrine.fixture.orm to DataFixtures in sevice.yml configuration.
services:
...
# makes classes in src/AppBundle/DataFixtures available to be used as services
# and have a tag that allows actions to type-hint services
AppBundle\DataFixtures\:
resource: '../../src/AppBundle/DataFixtures'
tags: ['doctrine.fixture.orm']
I tried #Alexander's solution but it's doesn't work for me.
I had resolved the same problem by adding the tag service to the class, Symfony doc on the services.yml file bundle:
BlogBundle/Resources/config/services.yml
Services:
...
# Fixtures services
BlogBundle\DataFixtures\ORM\PostFixture:
tags: [doctrine.fixture.orm]
...
My BlogBundle/DataFixtures/ORM/PostFixture.php class :
...
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
...
class PostFixture implements FixtureInterface
{
public function load(ObjectManager $manager)
{
...
}
}
Source Inspiration : Synfony doc -> Service container -> The autoconfigure Option
Hope it'll an alternative solution
Example for reusable bundle.
src/Acme/Bundle/UserBundle/DataFixtures/ORM/DataFixtures.php
<?php
namespace Acme\Bundle\UserBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class DataFixtures extends Fixture
{
/**
* Load data fixtures with the passed EntityManager
*
* #param ObjectManager $manager
*/
public function load(ObjectManager $manager)
{
#your code
}
}
in app/config/services.yml
Acme\Bundle\UserBundle\DataFixtures\:
resource: '../../src/Acme/Bundle/UserBundle/DataFixtures/'
append your fixtures data:
php bin/console doctrine:fixtures:load --append
In 4.0.1 I have to implement service configuration to show Symfony my DataFixtures folder:
in config/services.yaml
services:
...
App\DataFixtures\:
resource: '%kernel.project_dir%/src/DataFixtures'
tags: [doctrine.fixture.orm]
if my class IMPLEMENTS FixtureInterface and without this config if it is EXTENDS Fixture
~/dev/domain.lan/src/ProductBundle/DataFixtures/ORM/ProductFixture.php
<?php
namespace ProductBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use ProductBundle\Entity\Product;
class ProductFixture implements FixtureInterface
{
public function load(ObjectManager $manager)
{
// create 20 products! Bam!
for ($i = 0; $i < 20; $i++) {
$product = new Product();
$product->setName('Product name' . $i);
$manager->persist($product);
}
$manager->flush();
}
}
The problem is solved it was necessary to add a service: (app/config/services.yml)
services:
# Product service
ProductBundle\:
resource: '../../src/ProductBundle/*'
exclude: '../../src/ProductBundle/{Entity,Repository,Tests}'
use Doctrine\Bundle\FixturesBundle\Fixture
class ProductFixture extends Fixture implements FixtureInterface
see documentation: http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
After long research, found a solution.
This work with :
doctrine/doctrine-fixtures-bundle: ^3.0,
Symfony ^3.3
First
Define your Fixture.
<?php
namespace Where\MyFixtureBundle\FileFolder\IsLocated;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Nao\UserBundle\Entity\User;
class LoadData implements FixtureInterface
{
/**
* Load data fixtures with the passed EntityManager
*
* #param ObjectManager $manager
*/
public function load(ObjectManager $manager){
$object = new Entity();
$object->setFoo(bar)
...
$manager->persist($object);
$manager->flush();
}
}
Next, define a service in the bundle's service.yml file or directly in
"app/config/service.yml" (not recommanded)
# Fixtures service
your_service.name:
class: Full\Namespce\With\TheClassFixtureName
tags: [doctrine.fixture.orm] <-- important
Don't forget, just to be sure the following
composer du -o or composer dump-autoload -o
Try to execute your command now for load your data fixtures.
I also had to update the app/AppKernel.php and added the following the the bundles array:
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle()
After read above comment, i found solution inside #GuRu answer's :
"Second one: You need manually tag doctrine.fixture.orm to DataFixtures in sevice.yml configuration".
Then implements ORMFixtureInterface in your fixtures class.
. in fact, we have to add additionnal configuration inside services.yml to solve it.
Important to know, i notice this issue in version ~3.4 of symfony.
Best regard
I'm jumping here after several years just to document for myself the mixed of solution that I found in this thread.
I experienced the same issue and find the solution by using multiple answers and I hope it will help.
This were my code:
namespace App\DataFixtures;
use App\Entity\Book;
use App\Factory\{BookFactory, UserFactory};
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
UserFactory::new()->createOne([
'email' => 'user2#user.hr',
'roles' => ['ROLE_ADMIN']
]);
UserFactory::new()->createOne([
'email' => 'user3#user.hr',
'roles' => ['ROLE_USER']
]);
BookFactory::new()->createMany(25);
$manager->flush();
}
}
so I changed replaced extends Fixture ORMFixtureInterface. So new code looks like this:
namespace App\DataFixtures;
use App\Entity\Book;
use App\Factory\{BookFactory, UserFactory};
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
use Doctrine\Persistence\ObjectManager;
class AppFixtures implements ORMFixtureInterface
{
public function load(ObjectManager $manager): void
{
UserFactory::new()->createOne([
'email' => 'user2#user.hr',
'roles' => ['ROLE_ADMIN']
]);
UserFactory::new()->createOne([
'email' => 'user3#user.hr',
'roles' => ['ROLE_USER']
]);
BookFactory::new()->createMany(25);
$manager->flush();
}
}
then I went to services.yml and the I inserted this:
App\DataFixtures\:
resource: '%kernel.project_dir%/src/DataFixtures'
tags: ['doctrine.fixture.orm']
so services.yml file looks like this:
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.
# 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/'
- '../src/Entity/'
- '../src/Kernel.php'
App\DataFixtures\:
resource: '%kernel.project_dir%/src/DataFixtures'
tags: ['doctrine.fixture.orm']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
and then, like #Spartacvs1 suggested, in your terminal execute this
composer dump-autoload -o
How can I have a global variable in symfony template?
I did read this
but I prefer to fetch parameter from database, I think this service will be loaded on startup before it can fetch anything from db. Is it possible to do a trick to do so?
EDIT: Update in 2019 with Symfony 3.4+ syntax.
Create a Twig extension where you inject the Entity Manager:
Fuz/AppBundle/Twig/Extension/DatabaseGlobalsExtension.php
<?php
namespace Fuz\AppBundle\Twig\Extension;
use Doctrine\ORM\EntityManager;
use Twig\Extension\AbstractExtension;
use Twig\Extension\GlobalsInterface;
class DatabaseGlobalsExtension extends AbstractExtension implements GlobalsInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function getGlobals()
{
return [
'myVariable' => $this->em->getRepository(FuzAppBundle\Entity::class)->getSomeData(),
];
}
}
Register your extension in your Fuz/AppBundle/Resources/config/services.yml:
services:
_defaults:
autowire: true
autoconfigure: true
Fuz\AppBundle\Twig\Extension\DatabaseGlobalsExtension: ~
Now you can do the requests you want using the entity manager.
Don't forget to replace paths and namespaces to match with your application.
As of this day, the class signature has changed. You must implement \ Twig_Extension_GlobalsInterface, without it, your globals won't show up.
class MyTwigExtension extends \Twig_Extension implements Twig_Extension_GlobalsInterface
{ }
Bye!
you can register a twig extension
services:
twig_extension:
class: Acme\DemoBundle\Extension\TwigExtension
arguments: [#doctrine]
tags:
- { name: twig.extension }
And then in the TwigExtension you can do as follows:
class TwigExtension extends \Twig_Extension
{
public function getGlobals() {
return array(
// your key => values to make global
);
}
}
So you could get a value from the database in this TwigExtension and pass it to the template with the getGlobals() function
Stay away from global variables.
Instead make a custom twig extension then inject the database connection as a parameter.
Something like:
services:
acme.twig.acme_extension:
class: Acme\DemoBundle\Twig\AcmeExtension
arguments: [#doctrine.dbal.default_connection]
tags:
- { name: twig.extension }
Details:
http://symfony.com/doc/current/cookbook/templating/twig_extension.html
I am making an administration menu bundle. I want other bundles to be able to add menu items to the bundle, but I also want the menu items to be deleted when a bundle is removed. What would be the best way to do this?
I could create a 'regenerate admin menu' action that scans all bundles for a certain YML and then store that in cache or database.
Is there a better way to do this ?
You could use tags to find services from other bundles (or even the same) and use them to build menu from them in compiler pass.
In this example I will assume you have your menu defined as a service (I will use service id acme_menu.menu).
// src/Acme/MenuBundle/DependencyInjection/Compiler/BuildMenuCompilerPass.php
namespace Acme\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class BuildMenuCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('acme_menu.menu')) {
return;
}
$definition = $container->getDefinition('acme_menu.menu');
$taggedServices = $container->findTaggedServiceIds('acme_menu.item');
foreach ($taggedServices as $id => $attributes) {
$definition->addMethodCall(
'addMenuItem',
array(new Reference($id))
);
}
}
}
Register it with your menu bundle:
// src/Acme/MenuBundle/AcmeMenuBundle.php
namespace Acme\MenuBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Acme\MenuBundle\DependencyInjection\Compiler\BuildMenuCompilerPass;
class AcmeMenuBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new BuildMenuCompilerPass());
}
}
And every service tagged as acme_menu.item will be added to the menu - them method addMenuItem will be called on the menu on it's creation with the tagged service as a parameter. So simply define:
# services.yml
services:
acme_demo.menu.item1:
# ...
tags:
- { name: acme_menu.item }
acme_demo.menu.item2:
# ...
tags:
- { name: acme_menu.item }
I have to implement my own translation loader. I've used the tutorial on:
http://blog.elendev.com/development/php/symfony/use-a-database-as-translation-provider-in-symfony-2/
to implement my own translation loader.
I don't get any error's by my code, but the load function of my Loader never gets executed.
Is there any way to tell symfony which translation should be executed?
config.yml
translation.loader.db:
class: Mysk\TranslationBundle\Services\DBLoader
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: translation.loader, alias: db}
DBLoader.php
class DBLoader implements LoaderInterface {
private $transaltionRepository;
private $languageRepository;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager){
$this->transaltionRepository = $entityManager->getRepository("MyskTranslationBundle:LanguageTranslation");
$this->languageRepository = $entityManager->getRepository("MyskTranslationBundle:Language");
echo "yeah";
}
function load($resource, $locale, $domain = 'messages'){
die();
//Load on the db for the specified local
$language = $this->languageRepository->getLanguage($locale);
$translations = $this->transaltionRepository->getTranslations($language, $domain);
$catalogue = new MessageCatalogue($locale);
foreach($translations as $translation){
$catalogue->set($translation->getLanguageToken()->getToken(), $translation->getTranslation(), $domain);
}
return $catalogue;
}}
Any ideas?
Cheers
Timo
Aldo answered this already but just to make it an official answer and to help others:
You need to create a "fake" translation file to trigger your loader
From Symfony Dependency Injection Tags: "translation.loader"
[...] If you're loading translations from a database, you'll still need a resource file, but it might either be blank or contain a little bit of information about loading those resources from the database. The file is key to trigger the load method on your custom loader.
So you need to crate files of the form <domain>.<locale>.<loader-alias> in your translations folder app/Resources/translations/.
In your case one file would be app/Resources/translations/messages.en.db for English.
http://blog.elendev.com/page/3/#post-26 or http://blog.elendev.com/development/php/symfony/use-a-database-as-translation-provider-in-symfony-2/
Another issue here is that there are no LanguageRepository class in the example. You can use this example.
<?php
namespace TranslationBundle\Repository;
use Doctrine\ORM\EntityRepository;
class LanguageRepository extends EntityRepository
{
public function getLanguage($locale)
{
return $this->findOneBy(array('locale' => $locale));
}
}
I'd like to display new notifications on every page of my symfony 2 webapplication.
I was advised to use a Twig Extension for this. I've created a function getFriendRequests in that extension, but I don't know if it's good practice to get data through my custom repository in the twig extension: Right now it's giving me the error, that it can't find the getDoctrine method.
<?php
namespace Tennisconnect\DashboardBundle\Extension;
class NotificationTwigExtension extends \Twig_Extension
{
public function getFriendRequests($user)
{
$users = $this->getDoctrine()
->getRepository('TennisconnectUserBundle:User')
->getFriendRequests();
return count($users);
}
public function getName()
{
return 'notification';
}
public function getFunctions()
{
return array(
'getFriendRequests' => new \Twig_Function_Method($this, 'getFriendRequests'));
}
}
I don't think it is so bad to fetch your data directly from your twig extension. After all, if you don't do it here, you will need to fetch those records before and then pass them to the extension for display anyway.
The important point is to do the DQL/SQL stuff in the repository like you are already doing. This is important to separate database statements from other part of your project.
The problem you having is that the method getDoctrine does not exist in this class. From what I understand, you took this code from a controller which extends the FrameworkBundle base controller. The base controller of the FrameworkBundle defines this method.
To overcome this problem, you will have to inject the correct service into your extension. This is based on the dependency injection container. You certainly defined a service for your twig extension, something like this definition:
services:
acme.twig.extension.notification:
class: Acme\WebsiteBundle\Twig\Extension\NotificationExtension
tags:
- { name: twig.extension }
The trick is now to inject the dependencies you need like this:
services:
acme.twig.extension.notification:
class: Acme\WebsiteBundle\Twig\Extension\NotificationExtension
arguments:
doctrine: "#doctrine"
tags:
- { name: twig.extension }
And then, in you extension, you define a constructor that receives the doctrine dependency:
use Symfony\Bridge\Doctrine\RegistryInterface;
class NotificationTwigExtension extends \Twig_Extension
{
protected $doctrine;
public function __construct(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
// Now you can do $this->doctrine->getRepository('TennisconnectUserBundle:User')
// Rest of twig extension
}
This is the concept of dependency injection. You can see another question I answered sometime ago about accessing services outside controller: here
Hope this helps.
Regards,
Matt
The same but with mongo:
in config.yml
services:
user.twig.extension:
class: MiProject\CoreBundle\Twig\Extension\MiFileExtension
arguments:
doctrine: "#doctrine.odm.mongodb.document_manager"
tags:
- { name: twig.extension }
and in your Twig\Extensions\MiFile.php
<?php
namespace MiProject\CoreBundle\Twig\Extension;
use Symfony\Component\HttpKernel\KernelInterface;
class MiFileExtension extends \Twig_Extension
{
protected $doctrine;
public function __construct( $doctrine){
$this->doctrine = $doctrine;
}
public function getTransactionsAmount($user_id){
return $results = $this->doctrine
->createQueryBuilder('MiProjectCoreBundle:Transaction')
->hydrate(false)
->getQuery()
->count();
}
Rest of mi code ...
}