I'm building a small-scale symfony project, as much for my own edification as anything else.
I have a Network class which extends Entity in a Doctrine ORM setup, and a bunch of users (also entities in a Doctrine setup). I've given some users the CREATE permission on the Network class, and that seems to be working. At least the exception is thrown when I expect it:
$securityContext = $this->get('security.context');
$objectId = new ObjectIdentity('class', 'Acme\\AcmeBundle\\Entity\\Network');
if(false === $securityContext->isGranted('CREATE', $objectId)) {
throw new AccessDeniedException("You do not have permission.");
}
But I'd like to check the permission in a twig template, something like this:
{% if is_granted('CREATE', 'Acme\\AcmeBundle\\Entity\\Network') %}
<li>
<a href="{{ path('network_new') }}">
Create a new entry
</a>
</li>
{% endif %}
My goal here is to only show the link if the user has permission to create a new network. But the is_granted() call seems to be returning true for all the users, not just the ones that I've explicitly granted CREATE to, or at least link is always appearing, even for users that have no ACL/ACE entries for the Network class.
It turns out that is_granted() expects an object as the second parameter. This is a Twig Extension that provides a classId() function to return an ObjectIdentifier object.
class ClassIdExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
'classId' => new \Twig_SimpleFunction('classId', array($this, 'classId'))
);
}
public function classId($class)
{
return new ObjectIdentity('class', $class);
}
public function getName()
{
return 'acme_classidextension';
}
}
Once it's registered as a service in Symfony, you can use it in Twig templates like so:
{% if is_granted('CREATE', classId('Acme\\AcmeBundle\\Entity\\Network')) %}
<li>
<a href="{{ path('network_new') }}">
Create a new entry
</a>
</li>
{% endif %}
And it works as expected.
Related
Is there a standard/preferred way in Symfony to pass common variables to the base template?
There are things that are on every page, like a username in the menu that I obviously don't want to have to remember to add for every controller.
My thought was to create a controller for the template and return the data. But wondering if there is something more built in to handle this.
return $this->render(
'#secured/account/profile.html.twig',
array('userForm' => $form->createView(),
'base' => call_base_layout_controller()
);
{# templates/account/profile.html.twig #}
{% extends '#secured/base.html.twig' %}
{% block body %}
{% endblock %}
I cannot find it in the current version docs but, as far as I know you can still access a lot directly from twig through the app twig variable defined automatically by the framework for every request https://symfony.com/doc/3.4/templating/app_variable.html
For example, you can get the current user's username as follows:
{{ app.user.username }}
app holds user, request, session, environment, and debug so you can put other values needed into the session or environment variables, and fetch/render those values directly in the template.
If the data that you want is related to the authenticated user, I would recommend what Arleigh Hix said
Otherwise you can create a class that extends AbstractExtension and fill it with your logic
Anything on this class can be accessed from all your twig pages
// src/Twig/AppExtension.php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('myGlobalData', [$this, 'myGlobalData']),
];
}
public function myGlobalData()
{
return "what ever you want ( ex: a query resualt..)";
}
}
Than on your twig template just call it like this
{{ myGlobalData() }}
Even if it retuns an array, u can access it.
I recently started using Symfony 4 and I am creating my first website with this wonderful framework right now.
I have a sidebar that should be displayed in about half of my routes and the content of the sidebar should be filled with some data from a database.
Currently I use DI in all this routes and pass the result of the injected repository to the template (which includes my sidebar.html.twig) for the route.
public function chalupaBatman(FancyRepository $repository)
{
$sidebarObjects = $repository->getSidebarObjects();
$this->render('controllername/chalupabatman.html.twig', [
'sidebarObjects' => $sidebarObjects,
]);
}
I am wondering if there is a way to avoid this for every route I define in my controllers.
So far I found this topic on stackoverflow.
The User Mvin described my problem in a perfect way and also provided some solutions.
However there is still no answer to "what is the best practice" part also the topic is from 2017; therefor, the way to solve this may have changed in Symfony 4.
I ended up with a TwigExtension solution. I'll describe how to achieve it and it would be great if you guys could provide some feedback.
Let me know if I produce massive overhead or miss something essential ;-)
Alright, first of all I created a TwigExtension via command-line
php bin/console make:twig-extension AppExtension
And then I modified the class to look like this:
<?php
namespace App\Twig;
use App\Repository\ArticleRepository;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions(): array
{
return [
new TwigFunction('article_sidebar', [$this, 'getArticleSidebar'], ['needs_environment' => true, 'is_safe' => ['html']]),
];
}
public function getArticleSidebar(\Twig_Environment $twig)
{
$articleRepository = $this->container->get(ArticleRepository::class);
$archive = $articleRepository->myAwesomeLogic('omegalul');
return $twig->render('/article/sidebar.html.twig', [
'archive' => $archive,
]);
}
public static function getSubscribedServices()
{
return [
ArticleRepository::class,
];
}
}
In order to activate Lazy Performance so our Repository and the additional Twig_Environment doesn't get instantiated everytime when we use Twig
we implement the ServiceSubscriberInterface and add the getSubscribedServices-method.
Therefor, our Repo and Twig_Environment only gets instantiated when we actually call {{ article_sidebar() }} inside a template.
{# example-template article_base.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="row">
<div class="col-10">
{% block article_body %}{% endblock %}
</div>
<div class="col-2">
{{ article_sidebar() }}
</div>
</div>
{% endblock %}
Now I am able to define my templates for the article-routes like this:
{# example-template /article/show.html.twig #}
{% extends 'article_base.html.twig' %}
{% block article_body %}
{# display the article here #}
{% endblock %}
I’m facing a weird behavior from Symfony 2.5.5 (PHP 5.6.1), more specifically Twig. Here is a fragment of my template layout:
<nav>
{% render controller('SGLotteryGameBundle:Home:lastDraw') %}
<ol class="breadcrumb">
<li>{{ 'SuperWinner'|trans }}</li>
{% block bc %}{% endblock %}
</ol>
</nav>
This template worked fine until I added the render call. After that, Symfony reported:
An exception has been thrown during the rendering of a template
("Unable to generate a URL for the named route "sg_lottery_home" as such route does not exist.")
in /home/kevin/Prog/PHP/SG2/src/SG/Lottery/GameBundle/Resources/views/layout.html.twig at line 70.
Of course, the sg_lottery_home is defined and works well without the render block. If I comment the path generation of this route, the immediate next one fails. Routes before the tag are rendered without any issue.
Here is the SGLotteryGameBundle:Home controller:
<?php
namespace SG\Lottery\GameBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
class HomeController extends Controller
{
/**
* #Template
*/
public function indexAction()
{
return [];
}
public function lastDrawAction()
{
return new Response('Dummy');
}
}
I tried replacing {% render ... %} by {{ render(...) }}, without any change.
Important note: it only happens when I’m logged in.
Apparently, it was caused by JMSI18nRoutingBundle generating an error while retrieving the user's locale: the available locales were en and fr and the user's locale fr_FR. I have no idea how the {{ render(...) }} call interacted with that.
I created a small website with 4 pages, means 4 navigation points. There is always one of these navigation points set as active in css:
class="active"
All these 4 pages inherit the layout.html.twig file where the navigation is defined.
Which is the easiest way to change the class ("active") class depending on the navigation point choosen?
Thanks in advance for your help!
As I don't have that many navigation points I solved the problem with a if tag of twig.
<a href="{{ path('dbe_underConstruction') }}" title="Home"
{% if app.request.get('_route') == 'dbe_underConstruction' %} class="active"{% endif %}>Home </a>
This might be not that a clear solution, but it works fine :-)
You can use the KnpMenuBundle. For example in src/Foo/BarBundle/Menu/MenuBuilder.php :
<?php
namespace Foo\BarBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\Request;
class MenuBuilder
{
private $factory;
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
public function createMyMenu(Request $request)
{
$menu = $this->factory->createItem('root');
$menu->setChildrenAttributes(array('class' => 'my-menu'));
// Always visible
$menu->addChild('Home', array('route' => 'foo_bar_homepage'));
// According to the current route
switch($request->get('_route')) {
case 'item_list':
$menu->addChild('Items')
->setCurrent(true);
break;
case 'item_show':
$menu->addChild('Items', array('route' => 'item_list'));
$menu->addChild('Show')
->setCurrent(true);
break;
// ...
}
}
The active class will be available automatically when calling setCurrent() method.
The documentation is available here, and you can find another tutorial here.
You could define the current menu entry:
{% set current_menu = "home" %}
And then check what the current menu entry is:
<li class="nav-item{% if current_menu is same as("home") %} active{% endif %}">
I am using the following feature from propel http://www.propelorm.org/documentation/09-inheritance.html.
I am also using Symfony2 and Twig
I have a class structure using the above feature that looks something like this
class Event {}
class Birthday extends Event {}
class Walking extends Event {}
now I pass an event object to a twig template and I want to know what type of event it is
For instance I want to display an image of a cake if its a birthday and I want to display map routes if its walking event.
I cannot use instanceof in Twig as this feature does not exist.
Does anyone now why this does not exist?
and is there a way I can replicate this functionality without having to do something like
public function getType()
in each class, or
public function isBirthday()
in the event class.
I found this on github but it is of no use to me. I have commented on their to see if I can get an answer.
https://github.com/fabpot/Twig/issues/553
I share the opinion, that instanceof is nothing that should appear in a template. I use twig-tests for this case
class MyTwigExtension extends TwigExtension
{
public function getTests ()
{
return [
new \Twig_SimpleTest('birthday', function (Event $event) { return $event instanceof Birthday; }),
new \Twig_SimpleTest('walking', function (Event $event) { return $event instanceof Walking; })
];
}
}
And in the template
{% if event is birthday %}{# do something #}{% endif %}
An indirect way of accomplishing this would be testing the object for a method, if you know each inherited object has a unique method. Maybe your Birthday class has a getBirthday(), while your Walking class has a getMap()?
{% if yourobject.methodName is defined %}
//Stuff that should only output when the object in question has the requested method
{% endif %}
Using instanceof in a template is frowned upon from an architectual standpoint. If you find yourself in a position where you "need" it, you have probably uncovered a problem in your architecture. Your getType solution in your case is probably the best. You could still put that into the event base class and read it out the name of the implementing class.
Another solution :
class Event {
...
public function isInstanceOfBirthday() {
return $this instanceof Birthday;
}
}
then it will works with any class that inherit from
class Birthday extends Event {}
class Walking extends Event {}
then in your twig :
{{ event.isInstanceOfBirthday ? ... something for Birthday instance ... : ... something for Walking instance ... }}
OR
{% if event.isInstanceOfBirthday %}
... do something for Birthday instance ...
{% else %}
... do something for Walking instance ...
{% endif %}
I'm trying to make an index.html.twig that lists entities that are defined by the user, and only the fields that have been marked as 'addToListing' So I get to the point in which I don't know what I'm printing.
{% for entity in entities %}
<tr>
{% for heading in headings %}
<td>{{ attribute(entity, heading) }}</td>
{% endfor %}
</tr>
{% endfor %}
And heading happens to be a \DateTime :/ So for such case I'd need to | date('format') or some better solution.
Any advise on a clean solution for me?
I had similar problem, it was related to the inheritance in Hotel software. I had a base class "RoomEquipment", and inheritance with "Bed", "ElectricAppliances"....
class BaseRoomEquipment {}
class Bed extends BaseRoomEquipment {}
class ElectricAppliances extends BaseRoomEquipment {}
And of course, a class "Room" with relation OneToMany towards RoomEquipment.
On template, I wanted to render beds only, but Room has relation to base equipment, which includes beds and electric appliances.
Instead of testing in twig template, in Room class i have created a method getBeds, and thats it.
class Room {
private $equipment;
public getBeds()
{
$res = [];
foreach($this->getEquipment() as $eq) {
if ($eq instanceof Bed) $res[] = $eq;
}
return $res;
}
// Rest of class here....
}
And, of course, in Twig:
<div>
{% for Bed in Room.beds %}
{{ Bed.nbPersons }}
{% endfor %}
</div>
Thanks to Property Accessor component - this is possible. Now, your twig does not have to check about instance type, nor it does no