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));
}
}
Related
What is the best way to access configuration values inside an entity in a symfony 2 application?
I've searched about this and i've found two solutions:
Define the entity as a service and inject the service container to access configuration values
And this approach which defines a class in the same bundle of the entity with static methods that allows to get the parameter value
Is there any other solution? What's the best workaround?
Your entity shouldn't really access anything else, apart from associated entities. It shouldn't really have any connection outwardly to the outside world.
One way of doing what you want would be to use a subscriber or listener to listen to the entity load event and then pass that value in to the entity using the usual setter.
For example....
Your Entity
namespace Your\Bundle\Entity;
class YourClass
{
private $parameter;
public function setParameter($parameter)
{
$this->parameter = $parameter;
return $this;
}
public function getParameter()
{
return $this->parameter;
}
...
}
Your Listener
namespace Your\Bundle\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Your\Bundle\Entity\YourEntity;
class SetParameterSubscriber implements EventSubscriber
{
protected $parameter;
public function __construct($parameter)
{
$this->parameter = $parameter;
}
public function getSubscribedEvents()
{
return array(
'postLoad',
);
}
public function postLoad(LifecycleEventArgs $args)
{
/** #var YourEntity $entity */
$entity = $args->getEntity();
// so it only does it to your YourEntity entity
if ($entity instanceof YourEntity) {
$entity->setParameter($this->parameter);
}
}
}
Your services file.
parameters:
your_bundle.subscriber.set_parameter.class:
Your\Bundle\EventListener\SetParameterSubscriber
// Should all be on one line but split for readability
services:
your_bundle.subscriber.set_parameter:
class: %your_bundle.subscriber.set_parameter.class%
arguments:
- %THE PARAMETER YOU WANT TO SET%
tags:
- { name: doctrine.event_subscriber }
You shouldn't need a configuration in your entity.
For example you have File entity and you need to save a file represented by this entity to a disk. You need some parameter, let say "upload_dir". You can pass somehow this parameter to the entity and define a method inside this entity which saves a file to upload dir. But better way would be create a service which would be responsible for saving files. Then you can inject configurtion into it and in save method pass entity object as an argument.
I researched the How to Handle File Uploads with Doctrine and I don't want to hard-code the __DIR__.'/../../../../web/'.$this->getUploadDir(); path because maybe in future I will change the web/ directory. How to do it more flexible? I found this but it doesn't answer the question how to do it more flexible from inside the Entity
You shouldn't use entity class as a form model here. It's simply not suitable for that job. If the entity has the path property, the only valid values it can stores are: null (in case lack of the file) and string representing the path to the file.
Create a separate class, that's gonna be a model for your form:
class MyFormModel {
/** #Assert\File */
private $file;
/** #Assert\Valid */
private $entity;
// constructor, getters, setters, other methods
}
In your form handler (separate object configured through DIC; recommended) or the controller:
...
if ($form->isValid()) {
/** #var \Symfony\Component\HttpFoundation\File\UploadedFile */
$file = $form->getData()->getFile();
/** #var \Your\Entity\Class */
$entity = $form->getData()->getEntity();
// move the file
// $path = '/path/to/the/moved/file';
$entity->setPath($path);
$someEntityManager->persist($entity);
return ...;
}
...
Inside form handler/controller you can access any dependencies/properties from DIC (including path to the upload directory).
The tutorial you've linked works, but it's an example of bad design. The entities should not be aware of file upload.
To access the root directory from outside the controller you can simply inject '%kernel.root_dir%' as an argument in your services configuration.
service_name:
class: Namespace\Bundle\etc
arguments: ['%kernel.root_dir%']
Then you can get the web root in the class constructor:
public function __construct($rootDir)
{
$this->webRoot = realpath($rootDir . '/../web');
}
You can use a variable in your parameters.yml.
Like this you'll can change path when you want.
for example :
# app/config/parameters.yml
# Upload directories
upload_avatar_dir: /uploads/avatars
upload_content_dir: /uploads/content
upload_product_offer_dir: /uploads/product-offer
...
I handled this by creating an abstract class that Entities may extend if they are handling file uploads as described in the Symfony Documentation. I created the files array so I could create a copy of the existing file path in the set methods so it could be deleted off the file system on a successful update or delete without defining any additional properties in the Entity proper.
use Symfony\Component\HttpFoundation\File\File;
abstract class FileUploadEntity
{
private $files;
public function __set($name, File $value)
{
$this->files[$name] = $value;
}
public function __get($name)
{
if (!is_array($this->files)) $this->files = array();
if (!array_key_exists($name, $this->files)) {
return null;
}
return $this->files[$name];
}
public function getUploadRootDirectory()
{
return $this->getWebDirectory() . $this->getUploadDirectory();
}
public function getWebDirectory()
{
return __DIR__ . "/../../../../web/";
}
public function getUploadDirectory()
{
$year = date("Y");
$month= date("m");
return "images/uploads/$year/$month/";
}
public function getEncodedFilename($name)
{
return sha1($name . uniqid(mt_rand(), true));
}
// this should be a PrePersist method
abstract public function processImages();
// This should be defined as a Doctrine PreUpdate Method
abstract public function checkImages();
// this should be a PostPersist method
abstract public function upload();
// this should be a PostUpdate method and delete old files
abstract public function checkUpload();
// This should be a PostRemove method and delete files
abstract public function deleteFile();
}
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 ...
}
I've got an Entity that I want to associate with the users session.
I created a service so that I could reach this info from where ever.
in the service i save the entities id in an session variable
and in the getEntity() method i get the session variable and with doctrine find the entity and return it.
this way to the template i should be able to call {{ myservice.myentity.myproperty }}
The problem is that myservice is used all over the place, and I don't want to have to get it in every since Action and append it to the view array.
Is there a way to make a service accessible from all views like the session {{ app.session }} ?
The solution
By creating a custom service i can get to that from where ever by using
$this->get('myservice');
this is all done by http://symfony.com/doc/current/book/service_container.html
But I'll give you some demo code.
The Service
This first snippet is the actual service
<?php
namespace MyBundle\AppBundle\Extensions;
use Symfony\Component\HttpFoundation\Session;
use Doctrine\ORM\EntityManager;
use MyBundle\AppBundle\Entity\Patient;
class AppState
{
protected $session;
protected $em;
function __construct(Session $session, EntityManager $em)
{
$this->session = $session;
$this->em = $em;
}
public function getPatient()
{
$id = $this->session->get('patient');
return isset($id) ? $em->getRepository('MyBundleStoreBundle:Patient')->find($id) : null;
}
}
Register it in you config.yml with something like this
services:
appstate:
class: MyBundle\AppBundle\Extensions\AppState
arguments: [#session, #doctrine.orm.entity_manager]
Now we can as I said before, get the service in our controllers with
$this->get('myservice');
But since this is a global service I didn't want to have to do this in every controller and every action
public function myAction()
{
$appstate = $this->get('appstate');
return array(
'appstate' => $appstate
);
}
so now we go create a Twig_Extension
Twig Extension
<?php
namespace MyBundle\AppBundle\Extensions;
use MyBundle\AppBundle\Extensions\AppState;
class AppStateExtension extends \Twig_Extension
{
protected $appState;
function __construct(AppState $appState) {
$this->appState = $appState;
}
public function getGlobals() {
return array(
'appstate' => $this->appState
);
}
public function getName()
{
return 'appstate';
}
}
By using dependency injection we now have the AppState Service that we created in the twig extension named appstate
Now we register that with the symfony (again inside the services section inside the config-file)
twig.extension.appstate:
class: MyBundle\AppBundle\Extensions\AppStateExtension
arguments: [#appstate]
tags:
- { name: twig.extension }
The important part being the "tags", since this is what symfony uses to find all twig extensions
We are now set to use our appstate in our twig templates by the variable name
{{ appstate.patient }}
or
{{ appstate.getPatient() }}
Awesome!
Maybe you can try this in your action ? : $this->container->get('templating')->addGlobal($name, $value)
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