symfony parent template controller - symfony

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.

Related

Passing Twig variables to Vue.js in Symfony

I am running Symfony 4 app with Vue.js enabled. Is there any good practice to send my data from Twig templates to Vue.js components? Currently I have a number of data items for example on my header component, and HTML element data section looks like this:
<header-nav class="headnav headnav-fixed z-depth-1"
logo="{{ asset('build/images/site_logo.png') }}"
username="{{ app.user.name }}"
logout-url="{{ path('logout') }}"
logout-title="{% trans %} Logout {% endtrans %}"
instruction-url="{{ path('system_instruction_download') }}"
instruction-title="{% trans %} Download instruction {% endtrans %}"
current-locale="{{ app.request.getLocale()|upper }}"
v-bind:locales="{{ locales|json_encode }}"
>
Let's say I have a number of different URL's and other stuff. What is the best way to send the data? Should I first prepare an array of URL's on my controller? Which controller should it be if I want to prepare some global variables which will be used on my header, so they shouldn't be locked only on one controller.
Assuming that you render multiple "vue applications", you can define global variables with
1) twig configuration
Documentation says:
"Twig allows to inject automatically one or more variables into all templates.
These global variables are defined in the twig.globals option inside the main Twig configuration file"
2) You could create abstract controller with function merging variables
// MyAbstractController.php
protected function getTwigVariables(array $vars) {
$globals = [];
// ... fill $globals with your stuff
return array_merge(['globalVar1' => ], $vars);
}
// TestController extends MyAbstractController
public function indexAction() {
//...
return $this->render('viewPath.html.twig', $this->getTwigVariables([
'specificVariable' => 'variableContent'
]));
}
You could also embed controllers inside your main twig.
You can create headerAction, footerAction etc. and create subrequest for this actions.
For storing variables you can also use script tags
// twig
<script id="serverJson" type="application/json">{{ jsonContent|json_encode()|raw }}</script>
// serverJson.js
const configElement = document.getElementById("serverJson");
export default JSON.parse(configElement.innerHTML);
// ViewTemplate.vue
import serverJson from "path-to-serverJson.js"

Service to get/set current entity

I have a Project entity, which has a controller defining many routes:
projects/1
projects/1/foo
projects/1/bar
I need a service to provide the current project. The use case is that I have dependencies in my base twig templates which need to know the current project. i.e. a dropdown project selector that is outside the context of the template the current controller is serving.
I've tried creating a service getting route info with $container->get('router.request_context');, however, that only provides a path. I don't want to have to parse the path string if I don't have to.
What is the most proper approach?
If I understood you correctly the solution for your problem is rendering/embedding controller. Of course it's simplest, yet somehow acceptable solution for rendering parts of html with some custom logic apart from current template.
You can read about rendering/embedding controllers.
Some snippets...
Define controller:action (obviously the logic in my example is pretty straight forward):
/**
* Generate select input field.
*
* #Route("/widget", name="project_widget")
* #Method("GET")
*/
public function widgetAction()
{
$repo = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:Project');
// #NOTICE: Wee need master request info, because our widget
// is rendered and called as subrequest.
$masterRequest = $this->get('request_stack')->getMasterRequest();
// #NOTICE: You need second parameter as default value in case there is no id in masterRequest route.
$currentProjectId = $masterRequest->get('id', 0);
$currentProject = $repo->find($currentProjectId);
$projects = $repo->findAll();
return $this->render('project/widget.html.twig', [
'currentProject' => $currentProject,
'projects' => $projects,
]);
}
Then you need to create the template project/widget.html.twig for it:
<div class="widget_project_selection">
{% if projects is not empty %}
<select name="widget_project_selection" id="widget_project_selection">
<option value="">None</option>
{% for project in projects %}
<option value="{{ project.id }}"
{# #NOTICE: If this is current project, select it #}
{{- currentProject and project.id == currentProject.id
? 'selected="selected"'
: '' -}}
>
{{- project.title -}}
</option>
{% endfor %}
</select>
{% else %}
<span>{{ 'Sadly, no projects yet :('|trans }}</span>
{% endif %}
</div>
and at last (but not least) render it somewhere like in base.html.twig:
{{- render(controller('AppBundle:Project:widget')) -}}
I've created for you a Github repo as reference. It's a small Symfony app with similar setup. You can even run it if you like, don't forget about dependencies and database update thou. Entry point is /app_dev.php/project/
Take a look at widgetAction, widget template and example usage in base.html.twig.
EDIT: But that's not everything. You said you need a service. If for some reason rendering/embedding controller is not an option for you or you really whant to use a Service (as in Dependency Container) you can extend Twig and use the full power of services.
I've also implemented a Twig Filter as example to show you the real power of Twig Extensions in here and here (usage in templates).
Check out Twig Extension and Extending Twig for more info about Twig Extensions.
Also check out service.yml for service and extension definitions - if you are not using Symfony3.3+, there will be some additional work to do - defining service and extension directly.
In your controller, you can use type hinting to load the "current" entity via the route.
For example:
#myrouter.yml
current_project:
path: /projects/{project}
Separately, your controller...
//mycontroller.php
public function myControllerAction(Request $request, Project $project)
{
//$project is entity (assuming valid) loaded via the route above
return $this->render('mytemplate.twig', ['project' => $project]);
}

Checking class scope ACLs in Twig templates

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.

Using repository class methods inside twig

In a Symfony2.1 project, how to call custom entity functions inside template? To elaborate, think the following scenario; there are two entities with Many-To-Many relation: User and Category.
Doctrine2 have generated such methods:
$user->getCategories();
$category->getUsers();
Therefore, I can use these in twig such as:
{% for category in categories %}
<h2>{{ category.name }}</h2>
{% for user in category.users %}
{{ user.name }}
{% endfor %}
{% endfor %}
But how can I get users with custom functions? For example, I want to list users with some options and sorted by date like this:
{% for user in category.verifiedUsersSortedByDate %}
I wrote custom function for this inside UserRepository.php class and tried to add it into Category.php class to make it work. However I got the following error:
An exception has been thrown during the rendering of
a template ("Warning: Missing argument 1 for
Doctrine\ORM\EntityRepository::__construct(),
It's much cleaner to retrieve your verifiedUsersSortedByDate within the controller directly and then pass it to your template.
//...
$verifiedUsersSortedByDate = $this->getYourManager()->getVerifiedUsersSortedByDate();
return $this->render(
'Xxx:xxx.xxx.html.twig',
array('verifiedUsersSortedByDate' => $verifiedUsersSortedByDate)
);
You should be very carefull not to do extra work in your entities. As quoted in the doc, "An entity is a basic class that holds the data". Keep the work in your entities as basic as possible and apply all the "logic" within the entityManagers.
If you don't want get lost in your code, it's best to follow this kind of format, in order (from Entity to Template)
1 - Entity. (Holds the data)
2 - Entity Repositories. (Retrieve data from database, queries, etc...)
3 - Entity Managers (Perform crucial operations where you can use some functions from your repositories as well as other services.....All the logic is in here! So that's how we can judge if an application id good or not)
4 - Controller(takes request, return responses by most of the time rendering a template)
5 - Template (render only!)
You need to get the users inside your controller via repository
$em = $this->getDoctrine()->getEntityManager();
$verifiedusers = $em->getRepository('MYBundle:User')->getVerifiedUsers();
return array(
'verifiedusers' => $verifiedusers,
);
}

How to get the current page name in Silex

I'm wondering how to get the current page name, basically 'just' the last parameter in the route (i.e. /news or /about). I'm doing this because I want to be able to have the current page in the navigation highlighted.
Ideally, I'd like to store the current page name in a global variable so that in Twig I can just compare the current page name against the link and add a class accordingly.
I can't figure out how to add the current page name to a global variable though. I've tried using something like this:
$app['twig']->addGlobal('current_page_name', $app['request']->getRequestUri());
at the top of my app.php file, but an 'outside of request scope' error. But I wouldn't like to have to include this in every route.
What's the best way to do this?
If you put it into an app-level before middleware like this, that'll work:
$app->before(function (Request $request) use ($app) {
$app['twig']->addGlobal('current_page_name', $request->getRequestUri());
});
The "page name" part of your question is unclear, are you looking for the current route's name? You can access that via $request->get("_route") even in the before middleware, as it gets called when routing is already done.
You could also generate navigation list directly in stand alone nav twig template. And then import it in to the main template. Then you would only have to get silex to pass to the view the current page identifier. Simplest way... for example from Silex you would have to pass in the "path" variable to your view. Probably it would more convenient to to fetch nav_list from database and pass it in to twig template as global array variable instead. However this example is the simplest you could get to do what you intend.
nav.twig
{% set nav_list = [
["./", "home"],
["./contact", "contact"],
["./about", "about us"]
{# ... #}
] %}
{% set link_active = path|default("") %}
{% for link in nav_list %}
<li><a href="{{ link[0] }}" class="{% if link[0] == link_active %} activeClass {% endif %}" >{{ link[1] }}</a></li>
{% endfor %}
app.php
//...
$app->match('/about', function (Request $request) use ($app) {
return $app['twig']->render('about.twig', array(
'path' => './'.end(explode('/', $request->getRequestUri()))
));
});
//...

Resources