Symfony best practise: Where to place these constants? - symfony

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.

Related

how to get data from the entity Repository before every page render in symfony2?

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.

Symfony2 Circular reference

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'));

Symfony2, how to render controller's action in a twig template if controller has a constructor

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();

symfony2: define several php constants in bundle

I need defined several PHP constants and i need use this constants in my bundle (controller, custom classes, entities..) Where best place to add this constants, that would be convenient for them to get?
Why not just create a class to store your constants and use the use statement to autoload it where needed?
Define your constants...
namespace My\CoolBundle\Constants;
class ConstantlyCool {
const DEFAULT_COOLNESS_LEVEL = "newbie";
const MAX_COOLNESS_LEVEL = "phpisuber01";
}
Now use them...
namespace My\CoolBundle\Controller;
use My\CoolBundle\Constants\ConstantlyCool;
class CoolController extends Controller {
public function awesomeAction() {
// Do cool stuff
$cool_level = ConstantlyCool::DEFAULT_COOLNESS_LEVEL;
return new Response(/* blah blah */);
}
}
The best solution for my is setting these constants in Bundle Class, like that:
<?php
namespace AppBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
const SITE_NAME = 'Watchdev AR';
}
And use twig functions constant http://twig.sensiolabs.org/doc/functions/constant.html invoke them
In whatever twig file:
{{ constant('AppBundle\\AppBundle::SITE_NAME') }}
I think this solution follow the best practices that Symfony (without entity in this case) http://symfony.com/doc/current/best_practices/configuration.html
class sample
{const TEST_NUM= 10;}

SonataAdminBundle show dashboard blocks depending on user

I'm using SonataAdminBundle with Symfony 2.2 and want to display the dashboard blocks depending on what user is logged in.
e.g.
Users with Group Superadmin will see blocks 'UserManagement' and 'Messages'
Users with Group Staff will see only block 'Messages'
I read the whole documentation especially the security doc but found no info on how to restrict the dashboard view. I already implemented a filter which will will show no entries in the list view of an entity class if the user's permissions are not enough.
But it would be way better to not show him the blocks at all.
Any ideas on how to do this ?
Well, for anyone running into this, I solved it by simply returning an empty Response in execute(). My use-case was one where I wanted to show a block to users with a specific role:
First I defined my service for using the security.context service like so:
sonata.block.service.foo:
class: Acme\DemoBundle\Block\FooBlockService
arguments: [ "sonata.block.service.foo", "#templating", "#doctrine", "#security.context" ]
tags:
- { name: sonata.block }
Then I defined the block service __construct() as:
//Acme\DemoBundle\Block\FooBlockService
public function __construct($name, EngineInterface $templating, $doctrine, $securityContext)
{
parent::__construct($name, $templating);
$this->doctrine = $doctrine;
$this->securityContext = $securityContext;
}
Finally, in the execute function the first thing I did was check for a specific role on the user like so:
//Acme\DemoBundle\Block\FooBlockService
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
if( $this->securityContext->isGranted("ROLE_ACME_FOO") === false )
{
return new Response();
}
//... more code
This works, but it feels like a hack. Mainly, because the Block goes through all of its stages, and only on the output it return nothing, meaning overhead. A better solution would be to somehow prevent the entire Block from being loaded, based on some custom code.
In conclusion, this isn't the best way, but it worked for me!
You can restrict access to block using roles, like this:
sonata_admin:
dashboard:
blocks:
- { position: top, type: your_service_block_name, class: 'col-xs-4', roles: [ROLE_SOME_NAME_HERE_, ROLE_SOME_NAME_HERE_2] }
You can check under SonataAdminBundle:Core:dashboard.html.twig how this roles works:
{% if block.roles|length == 0 or is_granted(block.roles) %}
<div class="{{ block.class }}">
{{ sonata_block_render({ 'type': block.type, 'settings': block.settings}) }}
</div>{% endif %}

Resources