Probably I have a problem with a loop in template.
services:
twig_menu:
class: Cms\PageBundle\Twig\Menu
arguments: ['#doctrine.orm.entity_manager', "#templating"]
Code php:
namespace Cms\PageBundle\Twig;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Templating\EngineInterface;
class Menu {
protected $em, $templating;
public function __construct(EntityManager $em, EngineInterface $templating){
$this->em = $em;
$this->templating=$templating;
}
public function show($typ){
$menu=$this->em->getRepository("CmsAdminBundle:Menu")->findBy(array('type_id'=>$typ));
return $this->templating->render("menu.html.twig", array('links'=>$menu));
}
}
Template:
<ul>
{% for link in links %}
<li>{{ link.name }}</li>
{% endfor %}
</ul>
When I cleared cache on the first refresh it is ok, next I get this error:
Circular reference detected for service "templating", path:
"templating -> twig -> twig_menu".
templating needs twig, twig needs twig_menu and twig_menu needs templating. Hence your circular reference problem. It might be because you're in dev mode, where Twig has a lot more dependencies, because of the profiler.
Fabien Potencier himself has answered this problem on GitHub by saying "Just inject the service container and get Twig from that". It's a quick and dirty solution, but it should work without any serious penalties.
But because injecting the service container is a code smell, you might want to avoid it. The deeper (more correct) solution is to refactor so that twig doesn't depend on twig_menu, but without knowing your entire project, it's hard to say how you could do that.
Inject the twig service, rather than the templating service. #twig is the service name.
Instead of injecting the templating service within the twig_menu service's constructor you could provide it using a setter method. For example:
public function setTemplating(TwigEngine $templating)
{
$this->templating = $templating;
}
Then in your controller use:
$this->get('twig_menu')->setTemplating($this->get('templating'));
Related
Q1.I want to count the unread messages before every page rendered ,and add the number into twig template.i don't know how to make it
Q2.i have read the symfony tutorial,it seems that i will make a service ,but i don't know how to do it.is the following code right? and i don't know what to write at the seconde argument
namespace App\RepairBundle\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
use App\RepairBundle\Entity\RepairMessageRepository;
class CountUnReadMessage
{
protected $container;
protected $messageRepository;
public function __construct(ContainerInterface $container,RepairMessageRepository $messageRepository)
{
$this->container = $container;
$this->messageRepository = $messageRepository;
}
public function countUnRead()
{
$number = $this->messageRepository->countByUnread($this->container->get('security.token_storage')->getToken()->getUser()->getId());
return $number;
}
}
parameters:
app.repair.count_unread_message: App\RepairBundle\Service\CountUnReadMessage
services:
app.repair.count_unread_message:
class: %app.repair.count_unread_message%
arguments:
- #service_container
- #
If piece of the twig template containing message counter similar in all templates, you can move it to separate template and call service inside this template. You steps to achieve this might look like this:
Write service for getting message counter (you almost got it, but try to avoid injecting whole container in servce since it is a very bad practice. In this case, i think you could inject only security.token_storage)
Make this service visible in twig templates by declare it in config file.
config.yml
twig:
globals:
count_read_message: #app.repair.count_unread_message
In your separate twig file call this service
message_block.html.twig
{{ count_read_message.countUnRead() }}
And include this twig file to needed template (better idea would be keep main template for most of templates and include you file in this template, but this dependenced of template structure)
I hope you got the main idea =)
P.S. Answering for Q2 - it is #doctrine.orm.entity_manager
If you want to inject repository make another service with your repository:
app.message_repository:
class: Doctrine\ORM\EntityRepository
factory: ["#doctrine.orm.default_entity_manager", getRepository]
arguments:
- App\RepairBundle\Entity\RepairMessage
Then in your service:
app.repair.count_unread_message:
class: %app.repair.count_unread_message%
arguments:
- #service_container
- #app.message_repository
BTW you don't need container, inject only security.token_storage instead of container.
In my dev environment after adding a response listener the content type of my css files switched from text/css to text/html. My goal is just to add some caching headers to all responses of my symfony application.
Service definition:
response_listener:
class: AppBundle\EventListeners\ResponseListener
tags:
- { name: kernel.event_listener, event: kernel.response }
Service class:
<?php
namespace AppBundle\EventListeners;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class ResponseListener
{
public function onKernelResponse(FilterResponseEvent $event)
{
// while testing I do nothing special here
$response = $event->getResponse();
$event->setResponse($response);
}
}
Assetic config:
assetic:
debug: "%kernel.debug%"
use_controller: true
bundles: [ AppBundle ]
filters:
cssrewrite: ~
Twig tag:
{% stylesheets
'bundles/app/css/login.css'
filter='cssrewrite' %}
<link rel="stylesheet" type="text/css" href="{{ asset_url }}"/>
{% endstylesheets %}
With text/html as the content type the css is not rendered.
Removing just the service definition makes it work correctly again.
In prod environment it works with or without the response listener and js files are no problem at all.
Any ideas?
I had a really similar issue and although I didn't went to trace it exactly on the core bundles of symfony, I think this is due to the fact that, on development instance, assetic do use a controller to render the CSS.
I actually stopped looking at the moment I found, that, like you, my config_dev.yml stated :
assetic:
use_controller: true
So I figured out that when you try to play with the response object in symfony, the requested format gets somehow lost while listening on the response. Symfony's own ResponseListener seems to set text/html as a defaullt because there is no content type defined.
The fix is quite easy though :
In the class ResponseListener, our listening function become
public function onKernelResponse(FilterResponseEvent $event)
{
$response = $event->getResponse();
// We do get the params of the route
$route_params = $event->getRequest()->get('_route_params');
// And we figure out if it should be in a specific format
if(isset($route_params['_format'])) {
// getMimeType function on the Request object come in handy for this job
$format = $event->getRequest()->getMimeType($route_params['_format']);
// Then we just reinstate the right Content Type
$response->headers->set('Content-Type', $format);
}
$event->setResponse($response);
}
While you will never have this issue in production environnement like you state it, because your assets are dumped by the command line assetic tool, which is not a controller.
And actually, if you do something like this in your ResponseListener
die($event->getRequest()->get('_controller'));
And that you navigate to a css file by its direct url you would see this display :
Which totally validate the fact that the css gets constructed on the fly by the render view of assetic bundle controller when you have that configuration stated above.
From the official documentation (http://symfony.com/doc/current/quick_tour/the_view.html#embedding-other-controllers) I learned how to embed a Controller in a Twig template.
The problem appears when the Controller has injected properties. Is there a way to use Twig's render(controller()) function with Controllers that have a constructor?
When I try following:
{{ render(controller(
'SomeBundle:Some:renderList',
{ 'request': app.request }
)) }}
I get this error:
Missing argument 1 for SomeBundle\Controller\SomeController::__construct()
Constructor for this Controller look like that:
public function __construct($container, SomeService $someService) {
parent::__construct($container);
$this->someService = $someService;
}
Injection of the container and someService is configured in service.yml.
So again, my question is: how to embed controller in Twig's template when this controller uses Dependency Injection?
UPDATE
I could do like so:
{{ render(app.request.baseUrl ~ '/some/route') }}
But I would like to avoid making routes.
UPDATE 2
Service definition from service.yml
some.controller:
class: SomeBundle\Controller\SomeController
arguments: ["#service_container", "#some.service"]
If you have defined your controller as a service, you need to "inject" it into twig in order to make it available and correctly instantiated.
I suggest to create twig global variable
#app/config/config.yml
twig:
globals:
cc: "#comparison.controller"
Then you can use one of the methods (actions?)
{{ cc.foo(aBarVariable) }}
Alternative answer
You could create a twig extension where you could inject your service in order to make it available for views
For controllers as service you just need to use the service name (#some.controller) and action (yourAction) rather than the controller shortcut (SomeBundle:Some:renderList) as you can see in the Sylius templates.
So it would be...
{{ render(controller('some.controller:yourAction')) }}
If you are Symfony 2.4+ you can make use of the request_stack to get the request rather than passing it in as an argument like..
$request = $this->container->get('request_stack')->getMasterRequest();
I'm wondering, where I should place constants, e.g. for mapping a status, in Symfony. I'm used to set them in a controller, but it doesn't feel right and I prefer the entity, but not really.
What's right?
It's not a "what do you think?"-Question, I really want to know the best-practise and appreciate explanation or linked source(s). Both work, for now.
Controller or
namespace my\bundle\Controller;
class MyController {
const STATUS_NEW = 1;
const STATUs_PENDING = 2;
// ...
}
Entity ?
namespace my\bundle\Entity;
class MyEntity {
const STATUS_NEW = 1;
const STATUs_PENDING = 2;
// ...
}
Example in twig:
{% set statusNew = constant('my\\bundle\\Controller\\MyController::STATUS_NEW')%} {# or \\Entity\\ #}
{% if data.status == statusNew %}
Hi, I'm new.
{% endif %}
Thanks in advance!
M.
IMHO the entity itself is a good place. For the twig approach, in my previous project, I create some helper method on the entity for check the status like :
namespace my\bundle\Entity;
class MyEntity {
const STATUS_NEW = 1;
const STATUs_PENDING = 2;
// ...
// For each const status
public function isNew(){
return $this->status == self::STATUS_NEW;
}
}
and use in the twig like:
{% if data.isNew %}{# more contract form: if data.new #}
Hi, I'm new.
{% endif %}
And you don't expose the status field outside the entity (incapsulate the logic of new).
Hope this help.
The answer is it depends, but there are now Symfony best practices; in particular there is a section on constants vs. configuration options that speaks to exactly what you're asking.
Use constants to define configuration options that rarely change.
The nice thing about defining constants in classes is that you can easily access them in Twig. For instance, if you have a Post class like the example in the best practices, and pass an instance of that as post to your template, you can access the constant via:
{{ constant('NUM_ITEMS', post) }}
Now, there are definitely times where you might want to specify configurable options - say if you're creating a reusable bundle and want to allow an override of these values. So again it depends, but for your case, Symfony recommends placing them in your entity.
The STATUS constants belong in the entity so that they can be reused in multiple contexts (multiple controllers, for example) without needing to redefine the constants in each controller.
class AdminExtension extends \Twig_Extension
{
// content...
}
I'm extending twig functionality by adding a class that adds new twig method. Inside this method I want to use current route. How can I do that?
I mean, for example, having:
www.someurl.com/prefix/controller1/aaa/bbb/ccc/ddd
I want to get controller1/aaa/bbb/ccc/ddd part inside the function I described.
Thanks!
You should register the extension as a service and then inject request_stack (as of Symfony 2.4) in the service:
my_extension:
class: ...
arguments: ["#request_stack"]
tags: [{ name: twig.extension }]
Then you can get the request by using RequestStack#getCurrentRequest() and you can get the current url by using Request#getUri().