This is the code that gets executed (as displayed in "code behind this page" section):
Controller Code
/**
* #Route("/hello/{name}", name="_demo_hello")
* #Template()
*/
public function helloAction($name)
{
$name = "whatever";
$this->render('AcmeDemoBundle:Demo:hello.html.twig',
array('name' => '123'));
//return array('name' => 'abc');
}
Template Code
{% extends "AcmeDemoBundle::layout.html.twig" %}
{% block title "Hello " ~ name %}
{% block content %}
<h1>xHello {{ name }}!</h1>
{% endblock %}
The output is
xHello Raffael!
The URL: http://192.168.177.128/Symfony/web/app_dev.php/demo/hello/Raffael
And here is my problem:
When I uncomment the return within controller then "Raffael" is replaced with "abc" as expected.
But according to the Quicktour it is possible to determine the values of variables within the template via the render-Method.
To render a template in Symfony, use the render method from within a
controller and pass it any variables needed in the template:
$this->render('AcmeDemoBundle:Demo:hello.html.twig', array( 'name' =>
$name, ));
What's wrong?
Further down the quick tour implicitely reveales that you have to return the output of render:
public function helloAction($name)
{
return $this->render('AcmeDemoBundle:Demo:hello.html.twig',
array('name' => '123'));
}
Related
What I would like to achieve is to output some dynamic text coming from DB filled with unpredictable number of placeholders to be filled with some query parameters.
Basically it is an automation/notification system whereby upon user or admin's interaction with the website, some automation tasks will get triggered and added to DB. My closest shot at almost handling it is by using twig |replace filter in connection with twig extension. The problem is that I get to see the replaced text with the raw data not their parsed value. I guess it's better to look at my code. Your help is greatly appreciated.
DB Schema 'AutomationMsgTemplate, aka: amt'
raw_msg(text type) | format_keys(text type) | format_values(text type)
You are %USERNAME% | USERNAME | row.user.username
%No% %ORD_STATS% created | NO, ORD_STATUS | row.notif.x, row.order.y
My Service 'MsgFormatHelper' (facilitating twig ext)
public function renderMsgUsingSprintFormat(AutomationMsgTemplate $amt, GeneratedAutomationTask $gat): array
{
$formatter_keys = $amt->getFormatKeys();
$formatter_values = $amt->getFormatValues();
$formatter_keys_arr = explode(",", $formatter_keys);
$formatter_values_arr = explode(",", $formatter_values);
$formatter_ready = array_combine($formatter_keys_arr, $formatter_values_arr);
return $formatter_ready;
}
Twig extension
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions(): array
{
return [
new TwigFunction('msgSprint', [$this, 'renderMsg'])
];
}
public function renderMsg($amt, $gat): array
{
return $this->container
->get(MsgFormatHelper::class)
->renderMsgUsingSprintFormat($amt, $gat);
}
public static function getSubscribedServices(): array
{
return [
MsgFormatHelper::class
];
}
}
And finally inside the twig
{% for i, row in incompleteTasks %}
{% for amt in row.aet.automationMsgTemplates %}
// MOMENT OF TRUTH IS BELOW:
{% set format_arr = msgSprint(amt, row) %}
{% set processedMsg = amt.message|replace(format_arr) %}
{{ processedMsg }}
//nope, output is like: You are row.user.username
//sure enough below, as a debug test, works as intended
{# {% set processedMsg = temp.message|replace({'%USERNAME%': row.ats.studentCourse.student.username}) %} #}
{% endfor %}
{% endfor %}
I created a new plugin, with a simple custom frontend route. The page object is null when printed, so the elements inside are gone. I know I can just pass it from the renderStorefront function as a second parameter, but don't know what to fill in it because usually it is filled automatically.
The controller php file customStatusController.php:
/**
* #Route("/custom-status", name="frontend.store.customplugin.custom-status", options={"seo"="false"}, methods={"GET"})
*/
public function index(): Response
return $this->renderStorefront('#Storefront/storefront/index.html.twig');
}
The view twig file index.html.twig:
{% sw_extends '#Storefront/storefront/page/content/index.html.twig' %}
{% block base_header_flyout %}
{{ dump(page) }}
{% endblock %}
How can I pass/inherit the page object that comes from the cms with all the child objects?
You need to inject theShopware\Storefront\Page\GenericPageLoader and then create a NavigationPage from it.
So something like should do the trick ( did not test it it ):
<?php
/**
* #var GenericPageLoader
*/
private $genericPageLoader;
....
public function __construct(GenericPageLoader $genericPageLoader)
{
$this->genericPageLoader = $genericPageLoader;
}
/**
* #Route("/custom-status", name="frontend.store.customplugin.custom-status", options={"seo"="false"}, methods={"GET"})
*/
public function index(Request $request, SalesChannelContext $context): Response
$page = $this->genericPageLoader->load($request, $context);
$page = NavigationPage::createFrom($page);
return $this->renderStorefront('#Storefront/storefront/index.html.twig',
[
'page' => $page,
]);
}
Then you should have at least the HeaderPagelet, FooterPagelet etc. within the $page variable.
You can also take a look at our Blog Plugin how we created the detail page with a custom Controller.
However: You also should ask yourself if a custom Controller is really needed, or if a CmsDataResolver / CMS Element would do the trick also which is way more flexible.
this is my ConfigureShowFields using sonata bundle :
I want to transform my data in ConfigureShowFields to pdf , is possible ??
Its possible using
https://github.com/KnpLabs/KnpSnappyBundle
Firstly install and configure the KnpSnappyBundle
Then:
create custom action in your admin:
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('pdf', $this->getRouterIdParameter().'/pdf');
}
Create the logic for this action in the admin controller
public function pdfAction(Request $request)
{
$id = $request->get($this->admin->getIdParameter());
$object = $this->admin->getObject($id);
if (!$object) {
throw $this->createNotFoundException(sprintf('unable to find the object with id : %s', $id));
}
$this->admin->checkAccess('show', $object);
$this->admin->setSubject($object);
$response = $this->render(
'#App/Admin/pdf.html.twig',
[
'action' => 'print',
'object' => $object,
'elements' => $this->admin->getShow(),
],
null
);
$cacheDir = $this->container->getParameter('kernel.cache_dir');
$name = tempnam($cacheDir.DIRECTORY_SEPARATOR, '_print');
file_put_contents($name, $response->getContent());
$hash = base64_encode($name);
$options['viewport-size'] = '769x900';
$url = $this->container->get('router')->generate('app_print', ['hash' => $hash], Router::ABSOLUTE_URL);
$pdf = $this->container->get('knp_snappy.pdf')->getOutput($url, $options);
return new Response(
$pdf,
200,
[
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'filename="show.pdf"',
]
);
}
Create the pdf view to render the show without admin menu or headers.
App/Admin/pdf.html.twig
{% extends '#SonataAdmin/CRUD/base_show.html.twig' %}
{% block html %}
<!DOCTYPE html>
<html lang="en" dir="ltr">
{% block head %}
{{ parent() }}
{% endblock %}
<body style="background: none">
{% block sonata_admin_content %}
{{ parent() }}
{% endblock %}
</body>
</html>
{% endblock %}
Create a print controller in the app.
/**
* #Route(path="/core/print")
*/
class PrinterController
{
/**
* #Route(path="/{hash}", name="app_print")
*
* #param string $hash
*
* #return Response
*/
public function indexAction($hash)
{
$file = base64_decode($hash);
if (!file_exists($file)) {
throw new NotFoundHttpException();
}
$response = new Response(file_get_contents($file));
unlink($file);
return $response;
}
}
NOTE: This controller is used to use knpSnappy with a url instead of string, in order to avoid conflicts with assets, e.g. images etc. If you don't need print images or styles, simply use $this->get('knp_snappy.pdf')->generateFromHtml() to generate the pdf from the response instead of send to another url and remove the part when is used the cache to create a temporal rendered file.
I am new to Twig and need to check whether the way I use it in my MVC is the 'correct' way. I have a feeling that it isn't;
I want to have a controller for each region in my site and have each controller render their own twig template. I read about including twig templates inside twig templates such as:
main.twig
{% include 'header.twig' %}
{% include 'menu.twig' %}
{% include 'content.twig' %}
{% include 'footer.twig' %}
The problem with this is that I cannot run a separate controller for each region before the template is included. I would have to pass the variables for all regions as once to main.twig and I don't like to do that.
So I now do something like the following:
$regions=[];
//...preprocessing menu items here in a controller...
$template=$twig->loadTemplate('regions/menu.twig');
$regions['menu'] = $template->render(array(
'home' => 'Go to Home',
'contact' => 'Contact page'
));
//...other regions...
$template=$twig->loadTemplate('main.twig');
echo $template->render([
'regions'=>$regions
]);
And regions inside main.twig are then printed using the raw value: {{regions.menu|raw}}
This way I have full control over the data that is passed to each template which is what I want. However I have the feeling that I am now not using Twig the way it is supposed to, because I am saving rendered html in variables and then rendering it again.
If what I am trying to achieve is possible in a better way, please let me know.
I'm thinking it's causing a lot of overhead as you always will need to copy/paste the regions whenever you want to create a new page/controller. Idealy would be to use a main template with the includes and let your views extend from the base one.
base.twig.html
<!DOCTYPE html>
<html>
<head>
<title>{{ page.title | default('') }}</title>
<link rel="stylesheet" type="text/css" href="default.css" />
{% block css %}
{% endblock %}
</head>
<body>
{% block nav %}
<nav id="main">
{% for link in main.links %}
{{ link.title }}
{% endfor %}
</nav>
{% endblock %}
<div id="content">
{% block content %}
{% endblock %}
</div>
{% block javascript %}
{% endblock %}
</body>
</html>
{% extends "base.twig.html" %}
{% block content %}
<h1>{{ title }}</h1>
{% endblock %}
If you want to have a controller for each region you could create a helper class which calls all the controllers you need a returning an multi-dimensional array defined by the class name of the region.
This way your variables will never collide as you can access them by e.g. main.title / menu.title / title
(code is just pseudo-code, did not test/run it, just to give you an idea)
<?php
$regions = (new \Project\Regions\Container())->addRegion('Main')
->addRegion('Menu');
echo $twig->render('child.html', array_merge($regions->getParameters(), [
'title' => 'Hello World',
]);
class Container {
private $regions = [];
public function __construct($regions = []) {
$this->regions = $regions;
}
public function setRegions($regions = []) {
$this->regions = $regions;
return $this;
}
public function addRegion($region) {
if (!in_array($region, $this->regions)) $this->regions[] = $region;
return $this;
}
public function getParameters() {
$data = [];
foreach($this->regions as $region) {
$class = '\Project\Regions\\'.$region;
if (!class_exists($class)) continue;
$data[strtolower($region)] = (new $class())->getParameters();
}
return $data;
}
}
<?php
namespace Project\Regions;
abstract class Region {
public function getParameters() {
return [];
}
}
<?php
namespace Project\Regions;
class Page extends Region {
public function getParamters() {
return [
'title' => 'foo',
];
}
}
<?php
namespace Project\Regions;
class Menu extends Region {
return [
'title' => 'bar',
];
}
I have an entity Playlist that is related to another call Items.
I want to deploy on a twig template id of each of the items.
class Playlist
{
private $items;
public function __construct()
{
$this->items = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addItem(\Publicartel\AppBundle\Entity\PlaylistContent $content)
{
$content->setPlaylist($this);
$this->duration += $content->getDuration();
$this->items->add($content);
return $this;
}
public function removeItem(\Publicartel\AppBundle\Entity\PlaylistContent $content)
{
$this->items->removeElement($content);
$this->duration -= $content->getDuration();
}
public function getItems()
{
return $this->items;
}
}
I want to deploy in the Playlist form the id of the item.
I've tried like so:
{% for content in edit_form.items %}
{{ content.getId() }}
{{ content.id() }}
{% endfor %}
But I get the following error:
Method "getId" for object "Symfony\Component\Form\FormView" does not
exist Method "id" for object "Symfony\Component\Form\FormView" does
not exist
I've added the id attribute to my FormType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id')
->add('position')
->add('duration')
->add('playlist')
->add('content')
;
}
But I get the following error:
An exception has been thrown during the rendering of a template
("Catchable Fatal Error: Object of class
Symfony\Component\Form\FormView could not be converted to string")
I've also tried:
// Controller
$entity = $em->getRepository('PublicartelAppBundle:Playlist')->find($id);
return $this->render('PublicartelAppBundle:Playlist:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
// Twig
{{ entity.items.id }}
But that too throws an error:
Method "id" for object "Doctrine\ORM\PersistentCollection" does not exist
Thanks!
Solution 1
Last part of your solution is the good one
$entity = $em->getRepository('PublicartelAppBundle:Playlist')->find($id);
return $this->render('PublicartelAppBundle:Playlist:edit.html.twig', array(
'entity' => $entity,
[...]
));
What isn't good is that snippet of code
{{ entity.items.id }}
You have to cycle over all items before print id out (from your error, is pretty clear)
{% for item in entity.items %}
{{ item.id }}
{% endfor %}
Solution 2 (Didn't tested)
You can, of course, access also data from underlying object of a form, without pass it from the controller. So you can change your snippet of code from
from
{% for content in edit_form.items %}
{{ content.getId() }}
{{ content.id() }}
{% endfor %}
to
{% for content in edit_form.vars.data %}
{{ content.id() }}
{% endfor %}
Your error was that you were trying to access FormView (passed from controller) and not entity "binded" to the form