Zend expressive - Layout - php

In my layout (Twig), i'd like to retrieve a value from a Middleware authentication.
If i put, in templates.global.pĥp:
'twig' => [
'globals' => [
// Variables to pass to all twig templates
'auth' => (new \Zend\Authentication\AuthenticationService())->hasIdentity(),
],
],
And in layout default.html.twig
{% if auth %}
Connect
{% else %}
Not connect
{% endif %}
This code works, but, is it a good method ?
Thank you :)

It's not a good method. First of all, using the config files to set global template data is meant for static data. Creating a service in the config will fail if you want to cache the config. I don't know about the zend auction service, but it would be better to get it from the service manager or any other container you are using. This way you make sure that everywhere in your application the same service is being used.
For common variables or services which are needed in templates, I have a wrapper around the TemplateRenderer. So instead of calling the original template renderer, I call my own class and in there I populate the template with common data.
And you can also inject default parameters with TemplateRendererInterface::addDefaultParam. In any other middleware you can inject the templaterenderer, set the desired default data and later on access it in your templates.

Related

Access Control with Slim/PHP/Twig

I have a website based on Slim, Twig, PHP, SQL Server, which is running on an II.
I want to achieve some kind of GUI for an Administrator, where he can simply allow/disallow users to view specific parts (routes) of the website.
I can give a name to a route.
$Slim->map(['GET', 'POST'], '/route', function(){
//
})->setName('route_name');
also i can get all names from the routes like this
$allRoutes = $this->router->getRoutes();
foreach ($allRoutes as $key => $value) {
$target[$value->getPattern()] = [
'methods' => json_encode($value->getMethods()),
'middlewares' => json_encode($value->getMiddleware()),
'pattern' => $value->getPattern(),
'name' => $value->getName(),
];
}
and i can use the name in twig like this
link
I also can give a Slim route a middleware, which is called every call of the route.
My plan is to put the route names into a table, and specify which user is allowed to open which route.
Questions:
Is there any way to uniquely identify a route name? Because relying on a string (route name), which could easily be changed by the developer without getting notified to the website admin seems to be not the best idea.
Is there any way to somehow convince the twig template not to insert the -tag in the final html (which is served to the user), based on if the user is allowed (by the middleware) to open the route?
Actually there is a way to somewhat prevent name-mismatches between the route-names(strings) used in templates and in PHP/by the router.
Use PHP constants for route names.
This will further enable auto-completion in IDEs and give you a central location for your route-names.
A misnamed\non-existant route(-constant) will trigger an exception for both - PHP code and the template - instead of only during rendering of the template. This is the main benefit of using this method.
An interface can be used to store all your route-name constants.
Then use these constants with twig's constant function.
PHP interface
namespace Routing;
interface RouteName
{
public const HOMEPAGE = 'home';
// ... more routes
}
usage in PHP/Slim
use Routing\RouteName;
$Slim->map(['GET'], '/', function(){
// [..]
})->setName(RouteName::HOMEPAGE);
usage in Twig template
{{ path_for(constant('Routing\\RouteName::HOMEPAGE')) }}

Perform merge between views of two or more modules

I have a Phalcon PHP modular application. I am making an administrative interface to control which modules should be used in the system.
One module controls the application's default interface, while the other modules add functionalities.
I have the problem: when another module to enabled, it can add the HTML content to the other interface control module. In this way I would like to merge two or more views. I am using Volt as template engine.
Is this possible in Phalcon?
Note: This was asked on the official Phalcon forums. I answered it over there and it got accepted. I am just mirroring my answer so future readers can get an answer here without being redirected from StackOverflow. Phalcon forum mirror: https://forum.phalconphp.com/discussion/15891/perform-merge-between-views-of-two-or-more-modules
config.php
You will need to define your modules in the app/config/config.php file like so;
return new \Phalcon\Config([
// ...
'modules' => [
'module01',
'module02',
...
'moduleN',
],
// ...
]);
*Controller.php
Then, in your controller, you'd set a view property to store the active modules like so;
$this->view->modules_enabled = $this->di->get("config")->modules;
*.volt
And finally in your volt file, just check the module is in the array holding active modules, and if so, display the view using partials.
{% if module01 in modules_enabled %}
<div id="module">{{ partial("partials/module01") }}</div>
{% endif %}

How to profile code through Symfony StopWatch component

As per docs says:
The Stopwatch component provides an easy and consistent way to measure
execution time of certain parts of code so that you don't constantly
have to parse microtime by yourself.
I want to profile my REST API cURL calls and code execution in order to reduce the times but I am not sure how to do this. What I did on my code is put this piece of code:
$stopwatch->start('repsync', 'internal');
// the code goes here
$stopwatch->stop('repsync', 'internal');
But since this is a RESTful API I am not sure how to get profiler results. I was looking at StopWatch and notice getEvent and getSectionEvents but again I am not sure if I should call them directly and past the result to a Monolog logger or there is any other way to achieve this. In a few words: how do I see the profiler results when accessing routes trough a RESTful API? Any advice? Ideas?
This is how I handle it.
Use twig to render your api's data, in your Controller :
return $this->render('MyApiBundle:Default:debug.html.twig', array('data' => $data));
Then create the 'debug' template in Resources/views/Default/debug.html.twig
{% extends 'base.html.twig' %}
{% block body %}
Debug page
{{ dump(data) }}
{% endblock %}
Within your browser, call one of your API GET url. You will access Symfony's profiler and see your data.
For code execution time, you can listen to kernel.request and kernel.terminate events.

Using locateResource inside Twig Extension

I wish to use the following inside of a Twig Extension
$kernel = $container->getService('kernel');
$path = $kernel->locateResource('#AdmeDemoBundle/path/to/file/Foo.png');
but this involved passing in the Kernel, which is bad. Plus I could not get it to work anyway when trying this method.
How can I access a resources path within a Twig Extension?
The Extension is already a Service. I can use Assetic to give me the URL, but I really want the path.
I had a similar need: i needed to pass to the filter the url of an image to display it in a for loop and build a string.
I passed the URL directly to the filter in this way:
{% image '#AppBundle/Resources/public/images/my_asset.png' %}
{% set resolved_asset_url = asset_url %}
{% endimage %}
{{ my_var|filter_name(resolved_asset_url)|raw(html) }}
In this way the Twig template resolve the correct resource's URL, sets it as a variable, and then I pass it to the filter from inside the template itself, without having to deal with kernel or something else.
If you want just to serve a download, you should create a Route that accomplishes that task.
In this way, you'll call the locator inside the route, and a simple
{{ path('route_that_does_the_locator_thing') }}
will be fine.
If you need instead to include a file in your template (ex. CSS, JS..), you need to declare your file as an asset, maybe using Assetic.

What's the most convenient way to globally set 2 different form themes for app back-end and front-end

So, theming a form in Symfony2 is easy. You create a custom theme file and you add it to your config.yml file to load it. Done.
However, I have 2 different form themes. One for the front-end of the application and one for the back-end of the application.
I went through the documentation (http://symfony.com/doc/2.3/cookbook/form/form_customization.html) but couldn't find a good and easy way to do this.
When I add the theme to the config.yml file, I have the same theme in both front-end and back-end. I could also include the form within each view like this
{% form_theme form 'form_table_layout.html.twig' %}
However, that means I have to do it within each view.
Is there somehow a way to create a separate config file for front-end and back-end? Can I somehow indicate in the base template file which form theme should be used?
Anything else I could do?
If you use the Symfony2 default directory structure, i.e. you have a single kernel for both the frontend and the backend, you can only (as you mentioned) either set the form theme in each template, or use the same template application-wide by setting it in the config.yml file.
The alternative solution you mentioned, that is creating two base templates, each one setting a "global" form_theme tag would theoretically work. Create a base front-end.html.twig template for all your frontend pages with the following tag:
{% form_theme form 'form-front-end.html.twig' %}
That would work, but you would be forced to have a form variable in each inherited template. You would also not be able to set the theme to multiple forms in the same page.
You could improve the solution by checking if the form variable is defined before styling it:
{% if form is defined %}
{% form_theme form 'form-front-end.html.twig' %}
{% endif %}
or even better, if you want to be able to passing multiple form to the same template, you could do it by using a forms array:
{% if forms is defined %}
{% for form in forms %}
{% form_theme form 'form-front-end.html.twig' %}
{% endfor %}
{% endif %}
The good thing is that this would not throw any exceptions, even if you don't pass the variable at all, but you have to remember to put any forms to be rendered into the forms array.
Obviously, you would do the same thing for the back-end base template.
There may be better solutions, but in the meanwhile I hope this helps!
I also faced this problem some times ago, found this post, and followed the way as Andrea Sprega suggested. Recently I came out of a way which can save some repetitive typings.
Warning: This is somewhat "hackish", is probably not the best way, and is not guarantee to work in next framework release. You'll see why when you read below.
Goal: We want to have different default form templates depending on the application "environment" (the most common examples would be "frontend" and "backend). But fundamentally Symfony isn't aware of such "environment". Although it is possible to split the project into different environments and load different configurations, this is definitely an overkill if what we want is just having different form templates.
I think the best way would be an ability to set/override the default form themes in the base template. So we can have form theme A in frontend base template, and form theme B in backend base template. This sounds the best solution to me because as form theme is a "view" stuff, it makes perfect sense to have it changed in the view. However, the problem seems to be that, when the twig (template) code is executed, the form view is already initialized. So it would be too late to change the default form theme there. (I could be wrong here, because I didn't dig deep enough to have 100% confidence)
So I decided to do it another way. It works like this:
First, I made an assumption that all frontend controllers will extend the same base class (e.g. "BaseFrontendController"). Similarly, all backend controllers will extend the same BaseBackendController class. Here's how we distinguish a frontend and backend environment. This is actually the case in my project.
The default twig templates will be added to these base controller classes. It can be done via a method, or an annotation. In this post I'll use a public method.
Before the controller is executed, overwrite the defined default twig templates.
When the form view is initialized, it will use the default twig template defined in the controller.
Here's how it's done:
Firstly, the default form themes defined in your config.yml will be passed to the constructor of \Symfony\Component\Form\AbstractRendererEngine, and is assigned to the local instance $defaultThemes. This field is protected so that it can be used by its derived classes, but there is no setter to change its value.
So, we need to roll our own Symfony\Bridge\Twig\Form\TwigRendererEngine:
namespace AppBundle\Form\Twig;
use Symfony\Bridge\Twig\Form\TwigRendererEngine as BaseTwigRendererEngine;
class TwigRendererEngine extends BaseTwigRendererEngine
{
/**
* #param array $defaultThemes
*/
public function addDefaultThemes($defaultThemes)
{
$this->defaultThemes = array_merge($this->defaultThemes, $defaultThemes);
}
}
This custom renderer engine is plain simple - it just add a new method to append the default themes to the existing one. And this is why I say it's "hackish" - it will no longer work when the internal is changed.
Secondly, define an interface TwigTemplateProvider which is going to be implemented by the base frontend/backend controller classes:
namespace AppBundle\Form\Twig;
interface TwigTemplateProvider
{
/**
* #return array|string The form template path
*/
public function getDefaultFormTwigTemplates();
}
Thirdly, we need a listener which will run when the controller is executed.
<?php
namespace AppBundle\EventListener;
use AppBundle\Form\Twig\TwigRendererEngine;
use AppBundle\Form\Twig\TwigTemplateProvider;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class PerControllerFormTemplateListener
{
/**
* #var \Twig_Environment
*/
private $twig;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TwigTemplateProvider) {
/** #var \Symfony\Bridge\Twig\Extension\FormExtension $formExtension */
$formExtension = $this->twig->getExtension('form');
$engine = $formExtension->renderer->getEngine();
if ($engine instanceof TwigRendererEngine) {
$templates = (array)$controller[0]->getDefaultFormTwigTemplates();
$engine->addDefaultThemes($templates);
}
}
}
}
The listener will get the form template name (in string or array) supplied by controllers implementing TwigTemplateProvider interface. Then it will add it to the default theme list and pass it to the form renderer engine.
Now, wire them together by adding the followings to your services.yml:
parameters:
twig.form.engine.class: AppBundle\Form\Twig\TwigRendererEngine
services:
app.form.per_controller_template_listener:
class: AppBundle\EventListener\PerControllerFormTemplateListener
arguments: ["#twig"]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Here we set the %twig.form.engine.class% parameter to our own implementation, and added our event listener to the stack.
Using it is very simple. For example, in your base frontend controller, implement TwigTemplateProvider and add the following method:
public function getDefaultFormTwigTemplates()
{
return 'frontend/form_layout.html.twig';
}
Then this layout will be added to the form template stack when your front controller is executed.

Categories