Zf1/Zwig/Twig integration failed - zend-framework

I've got a problem integrating Twig and Zend Framework 1, with the Zwig library alb/zwig,
Versions are
alb/zwig version 1.0.1
twig/twig 1.14.2
zf1 1.12.3
The result is:
module controller action call is fine
layout.twig is properly displayed (parent() works)
index.twig specific block content is not displayed
Trials:
several downgrades, no step by step debug performed
application/configs/application.ini
twig.templateDir = APPLICATION_PATH "/modules/%module%/views/scripts/"
;twig.options.cache = APPLICATION_PATH "/../cache/twig"
autoloaderNamespaces[] = "Twig_"
autoloaderNamespaces[] = "Zwig_"
autoloaderNamespaces[] = "ZFDebug_"
resources.modules[] = ""
resources.layout.layoutPath = APPLICATION_PATH "/modules/Layout/views/scripts/"
resources.layout.layout = "layout"
application/Bootstrap.php
public function _initLayout() {
$layout = $this->getPluginResource("layout")->getLayout();
$layout->setViewSuffix("twig");
}
protected function _initTwig() {
Twig_Autoloader::register();
$config = Zend_Registry::get('config');
$templatePath = array();
$view = new Zwig_View(array(
'encoding' => 'UTF-8',
'helperPath' => array(
),
));
$loader = new Twig_Loader_Filesystem();
$d = new DirectoryIterator($config->resources->frontController->moduleDirectory);
foreach ($d as $fileInfo) {
zif (!$fileInfo->isDot() && $fileInfo->isDir()) {
$moduleName = $fileInfo->getFilename();
$templatePath =
str_replace(
'%module%', $moduleName, $config->twig->templateDir
);
$loader->addPath($templatePath, strtolower($moduleName));
}
}
$zwig = new Zwig_Environment($view, $loader, array(
'debug' => true,
'cache' => APPLICATION_PATH . '/cache/twig/',
//'auto_reload' => true,
));
$view->setEngine($zwig);
$view->doctype(Zend_View_Helper_Doctype::XHTML1_STRICT);
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view, array(
'viewSuffix' => 'twig',
));
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
return $view;
}
application/modules/Login/controllers/IndexController.php
class Login_IndexController extends Zend_Controller_Action
{
public function init()
{
/* Initialize action controller here */
}
public function indexAction()
{
// action body
$this->view->toto = "texte";
}
}
application/modules/Layout/views/scripts/layout.twig
{# empty Twig template #}
Titre de la page
{% block header %}
{%endblock header %}
{% block content %}
eéâà
{% endblock content %}
{% block sidebar %}
{% endblock sidebar %}
ceci est un contenu de page
child template
# cat application/modules/Login/views/scripts/index/index.twig
{% extends '#layout/layout.twig' %}
{% block content %}
{{ parent() }}
{{ toto }}
{% endblock content %}

Did you change the default view suffix?
$this->getHelper('viewRenderer')->setViewSuffix('twig');
This should also be settable from your config file.

I found the problem, in application/Bootstrap.php _initLayout() function and application.ini related conf must be removed. This conflicts with twig inheritance.
application/Booststrap.php
/*
public function _initLayout() {
$layout = $this->getPluginResource("layout")->getLayout();
$layout->setViewSuffix("twig");
}
*/
application.ini
;resources.layout.layoutPath = APPLICATION_PATH "/modules/Layout/views/scripts/"
;resources.layout.layout = "layout"

Related

Imported Twig macro displays nothing i.e. not working

I'm trying to implement some sort of macro autoloading.
The idea is to define a bunch of macros and use them on all the next template files.
Here's how I'm trying to do it:
<?php
define('ROOT_FRONT', '/path/to/files/');
define('LAYOUT_DIR', ROOT_FRONT . 'layout/');
include(ROOT_FRONT . 'lib/Twig/Autoloader.php');
Twig_Autoloader::register();
$twig_loader = new Twig_Loader_Filesystem(array(LAYOUT_DIR, ROOT_FRONT));
$twig = new Twig_Environment($twig_loader, array(
'charset' => 'ISO-8859-15',
'debug' => !!preg_match('#\.int$#', $_SERVER['SERVER_NAME']),
'cache' => $_SERVER['DOCUMENT_ROOT'] . '/cache/twig/'
));
$macro_code = '';
foreach(array_filter(
array_diff(
scandir(LAYOUT_DIR . 'macros/'),
array('..','.')
),
function($file)
{
return strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'twig'
&& is_file(LAYOUT_DIR . 'macros/' . $file);
}
) as $file)
{
$info = pathinfo($file);
$macro_code .= '{% import \'macros/' . $info['basename'] . '\' as macros_' . $info['filename'] . ' %}';
}
$twig
->createTemplate($macro_code)
->render(array());
$twig->display('index.twig', array());
If I have a file, say, macro/clearfix.twig, it will generate this template code, inside $macro_code:
{% import 'macros/clearfix' as macros_clearfix %}
The code inside macro/clearfix.twig is something like this:
{% macro clearfix(index, columns) %}
{% if index is divisible by(columns) %}
<div class="clearfix visible-md-block visible-lg-block"></div>
{% endif %}
{% if index is even %}
<div class="clearfix visible-sm-block"></div>
{% endif %}
{% endmacro %}
And then, inside the index.twig, I have this:
{{ macros_clearfix.clearfix(index=2, columns=6) }}
But nothing is displayed.
However, the following code works:
{% set index = 2 %}
{% set columns = 6 %}
{% if index is divisible by(columns) %}
<div class="clearfix visible-md-block visible-lg-block"></div>
{% endif %}
{% if index is even %}
<div class="clearfix visible-sm-block"></div>
{% endif %}
What could I possibly be doing wrong?
Am I misunderstanding something or applying this incorrectly?
TL;DR:
Twig requires you to load the macros inside the file where they will be used.
Just create custom functions to do what you want.
Twig (at least v1.30) doesn't implement macro inheritance.
This requires that you load every single macro you want to use on every single file.
The only way to do this is with functions, entirelly written in PHP.
This is what I've settled with:
index.php:
<?php
define('ROOT_FRONT', '/path/to/files/');
define('LAYOUT_DIR', ROOT_FRONT . 'layout/');
include(ROOT_FRONT . 'lib/Twig/Autoloader.php');
Twig_Autoloader::register();
$twig_loader = new Twig_Loader_Filesystem(array(LAYOUT_DIR, ROOT_FRONT));
$twig = new Twig_Environment($twig_loader, array(
'charset' => 'ISO-8859-15',
'debug' => !!preg_match('#\.int$#', $_SERVER['SERVER_NAME']),
'cache' => $_SERVER['DOCUMENT_ROOT'] . '/cache/twig/'
));
// ~ magic happens here ~
foreach(include(LAYOUT_DIR . 'fn.php') as $k => $fn)
{
$twig->addFunction(new Twig_SimpleFunction("fn_$k", $fn));
}
$twig->display('index.twig', array());
fn.php:
<?php
return array(
'clearfix' => function($index, $columns){
$html = '';
if(!($index % $columns))
{
$html .= '<div class="clearfix visible-md-block visible-lg-block"></div>';
}
if(!($index & 1))
{
$html .= '<div class="clearfix visible-sm-block"></div>';
}
return $html;
}
);
index.twig:
{{ fn_clearfix(index=2, columns=6) }}
This way, all your code is neatly indexed, new functions are created automatically and it is pretty easy to extend it to your liking.
Probably this is the worst idea, but it does the job.
Macros
As of Twig 2.0, macros imported in a file are not available in child templates anymore (via an include call for instance). You need to import macros explicitly in each file where you are using them.
From https://twig.symfony.com/doc/1.x/deprecated.html

Symfony 2.8 - display certain entity value in form field widget template

I am building a custom file upload widget where I display last uploaded filename. I created FormType class and in form/fields.html.twig I added the following:
{% block custom_document_widget %}
{% spaceless %}
{# here I want to include code to display filename #}
{# display file input #}
{% set type = 'file' %}
{{ block('form_widget_simple') }}
{% endspaceless %}
{% endblock %}
I know that the value of a current field can be parsed {{ form.vars.value }}, but in the end the field is file input and does not have the value of filename that was uploaded previously.
To store uploaded filename I have $filename variable in entity and would like to display it in field widget template. How can I approach it?
In the end I had to pass the filename as an option to embedded form that represented my FileType:
$builder
->add('resumeFile', CustomDocsType::class, array(
'required' => false,
'constraints' => array(
new File(array(
'mimeTypes' => array(
'application/pdf',
),
'mimeTypesMessage' => 'mimetype',
)),
),
'filename' => $trainee->getResumeOriginal(),
))
In my CustomDocsType:
class CustomDocsType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
parent::buildView($view, $form, $options);
$view->vars = array_merge($view->vars, array(
'filename' => $options['filename']
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'filename' => null
));
}
public function getParent()
{
return FileType::class;
}
}
And now I only had to acces the filename in template:
{{ form.vars.filename }}

Symfony 2.7 Form Entity type render multiple properties in form

I had this working previously but it stopped working with Symfony 2.7
What I want is to render an expanded/multiple entity choice list such that I display multiple custom properties. The goal is to list the choices as:
{name} - {description} More info
So I created a custom form type with "entity" as parent so I could customize the form rendering
<?php
namespace Study\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ScholarshipEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->setAttribute('dataType', $options['dataType']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'required' => false,
'dataType' => 'entity'
));
}
public function getAllowedOptionValues(array $options)
{
return array('required' => array(false));
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 'scholarship_entity';
}
}
I render the type as follows (it was just based off of the Twitter Bootstrap bundle template):
{% block scholarship_entity_widget %}
{% spaceless %}
{% if expanded %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default(''))}) %}
{% set label_attr = label_attr|merge({'class': (label_attr.class ~ ' ' ~ (widget_type != '' ? (multiple ? 'checkbox' : 'radio') ~ '-' ~ widget_type : ''))}) %}
{% if expanded %}
{% set attr = attr|merge({'class': attr.class|default(horizontal_input_wrapper_class)}) %}
{% endif %}
{% for child in form %}
{% if widget_type != 'inline' %}
<div class="{{ multiple ? 'checkbox' : 'radio' }}">
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{ form_widget(child, {'horizontal_label_class': horizontal_label_class, 'horizontal_input_wrapper_class': horizontal_input_wrapper_class, 'attr': {'class': attr.widget_class|default('')}}) }}
{{ child.vars.label.name|trans({}, translation_domain) }}
- {{ child.vars.label.description }}
More Information
</label>
{% if widget_type != 'inline' %}
</div>
{% endif %}
{% endfor %}
{{ block('form_message') }}
{% if expanded %}
{% endif %}
{% else %}
{# not being used, just default #}
{{ block('choice_widget_collapsed') }}
{% endif %}
{% endspaceless %}
{% endblock %}
Finally, I use my custom type in another form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('scholarships', new ScholarshipEntityType(), array(
'class' => 'StudyMainBundle:Scholarship',
'query_builder' => function(EntityRepository $er) use ($options) {
return $er->findAllByOfferingQueryBuilder($options['offering']);
},
'choice_label' => 'entity',
'multiple' => true,
'expanded' => true,
'label' => 'financial.scholarships'
))
;
}
The "property" I'm rendering is just the entity itself:
/**
* Scholarship
*
* #ORM\Table(name="scholarship")
* #ORM\Entity(repositoryClass="Study\MainBundle\Repository\ScholarshipRepository")
*/
class Scholarship
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* Get the Entity object for form rendering
*
* #return \Study\MainBundle\Entity\Scholarship
*/
public function getEntity()
{
return $this;
}
}
Unfortunately, it looks like my trick which was passing the entire Entity to Twig and letting me access properties is no longer working. There is some change where the label is rendered as a string (I changed 'property' to 'choice_label' above for 2.7, if that matters).
Error:
Catchable Fatal Error: Object of class Study\MainBundle\Entity\Scholarship could not be converted to string
Stack Trace:
1. in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 251 +
2. at ErrorHandler ->handleError ('4096', 'Object of class Study\MainBundle\Entity\Scholarship could not be converted to string', '/var/project/vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php', '251', array('choice' => object(Scholarship), 'key' => '0', 'label' => object(Closure), 'values' => array('2'), 'index' => array('Symfony\Bridge\Doctrine\Form\Type\DoctrineType', 'createChoiceName'), 'attr' => null, 'isPreferred' => array(), 'preferredViews' => array(), 'otherViews' => array(), 'value' => '2', 'nextIndex' => '2'))
in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 251 +
3. at DefaultChoiceListFactory ::addChoiceView (object(Scholarship), '0', object(Closure), array('2'), array('Symfony\Bridge\Doctrine\Form\Type\DoctrineType', 'createChoiceName'), null, array(), array(), array())
in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 185
Is there another way to achieve this?
I was thinking about the following (but don't know exactly how to do these or if it's worth looking into any of them):
transformers
custom type that derives from Choice and does what I want (maybe from a bundle)
using the choice list factory somehow
passing the entity as some additional field instead of the label (maybe the new 'choice_attr'?)
If I understood correctly the problem, you should implement the __toString() function in your entity, that will format the string you want to print in the Choice list for you entity.
For example:
function __toString() {
return sprintf("%s - %s", $this->type, $this->description);
}
Try to use the method AbstractType::buildView(FormView, FormInterface, array). There you can access the variables that get passed to the template.
I used it for a DaterangeType to declare separate ids and names for two date fields:
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['full_name_0'] = $view->vars['full_name'] . '[0]';
$view->vars['full_name_1'] = $view->vars['full_name'] . '[1]';
$view->vars['id_0'] = $view->vars['id'] . '_0';
$view->vars['id_1'] = $view->vars['id'] . '_1';
}
You can then access these values as standard twig variables.

Symfony2 multiple forms in one template

My app consists of Zones that can have many Devices.
When viewing a Zone, it needs to display a control for each Device in the Zone.
Each Device is completely independent, so embedding the Device forms in a Zone form seems unnecessary - I only want to deal with changes to one device at a time.
Currently I'm creating a form for each device and passing them to the Zone view template:
public function viewAction($zone_id)
{
$zone = $this->getZoneById($zone_id);
$forms = array();
foreach ($zone->getDevices() as $device) {
$forms[] = $this->createForm(new DeviceType(), $device)->createView();
}
return $this->render('AcmeBundle:Zones:view.html.twig', array('zone' => $zone, 'deviceForms' => $forms));
}
And then in the view template, I'm looping through the forms:
{% for form in deviceForms %}
{% include 'AcmeBundle:Devices:control.html.twig'
with {'zone':zone, 'form':form}
%}
{% endfor %}
This seems to be working ok, but I really need to change the template that renders based on the 'type' of Device. What's the cleanest way to do this? I can do something like:
{% if form.vars.data.type == 'foo' %}
{% include 'AcmeBundle:Devices:control-foo.html.twig'
with {'zone':zone, 'form':form}
%}
{% elseif form.vars.data.type == 'bar' %}
{% include 'AcmeBundle:Devices:control-bar.html.twig'
with {'zone':zone, 'form':form}
%}
{% endif %}
but this seems like putting too much logic in the template? It would be better to assign the template to render to the form object somehow, but I've no idea if this is possible?
You must add an option 'template' or whatever in the FormType via the controller,
In the FormType you must declare the default option 'template' and pass it the the form view.
public function viewAction($zone_id)
{
$zone = $this->getZoneById($zone_id);
$forms = array();
//You define a config for each type of device (you should use parameters)
$templates = array(
'foo' => 'AcmeBundle:Devices:control-foo.html.twig',
'bar' => 'AcmeBundle:Devices:control-bar.html.twig',
);
foreach ($zone->getDevices() as $device) {
//define your template here.
$type = $device->getType();
//add a template option in the form.
$options['template'] == $templates[$type];
$forms[] = $this->createForm(new DeviceType(), $device, $options)->createView();
}
return $this->render('AcmeBundle:Zones:view.html.twig', array('zone' => $zone, 'deviceForms' => $forms));
}
Now in the DeviceType you should set the defaults options in the form, they will be merged with options we create in the controller.
public function getDefaultOptions(array $options) {
return array(
//...other options...
//this is the default template of this form
'template' => 'AcmeBundle:Devices:control.html.twig'
);
}
Then set the attribute on the form in the Builder
public function buildForm(FormBuilder $builder, array $options)
{
$builder->setAttribute('template', $options['template']);
//...your fields here...
}
And finally, set the var template in the view.
public function buildView(FormView $view, FormInterface $form)
{
$view->set('template', $form->getAttribute('template'));
}
Now you can read the "template" option in twig, and include the corresponding template
{% for form in deviceForms %}
{% include form.get('template') with {'zone':zone, 'form':form} %}
{% endfor %}
Do not forget to add lines at the beginning of the FormType
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormBuilder;

edit Symfony2 big entity in form with tabs

I'm building form using Sf2's form builder.
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('firstName')
->add('lastName')...
The Entity has a lot of fields and I'd like to put them in jQuery UI Tabs. But in twig template I'd like to use single command
<form action="#" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" value="Save"/>
</form>
What is best solution?
edit **
To be more conrete: I have 4 fields: firstName, lastName, birthDate, deathDate. I want first 2 fields to be on first tab and the last 2 fields to be on second tab. I want to keep way of rendering the form as mentioned earlier.
I though of a solution to create my own fields not conneceted to underlaying object which will render required html tags (h3, div, etc).
I defined my own field called 'Tab' and add it when new tab should appear.
<?php
//\src\Alden\xyzBundle\Form\Type\TabsType.php
namespace Alden\BonBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormValidatorInterface;
use Symfony\Component\Form\Form;
class TabsType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$builder->setAttribute('starting', $options['starting']);
$builder->setAttribute('ending', $options['ending']);
$builder->setAttribute('header', $options['header']);
}
public function buildView(FormView $view, FormInterface $form)
{
$parent = $form->getParent();
if (is_null($parent->getParent()))
{
$tabs = $this->findTabs($parent);
}
else
{
$tabs = array();
}
$view->set('starting', $form->getAttribute('starting'));
$view->set('ending', $form->getAttribute('ending'));
$view->set('header', $form->getAttribute('header'));
$view->set('tabs', $tabs);
}
public function getDefaultOptions(array $options)
{
return array(
'property_path' => false,
'starting' => true,
'ending' => true,
'header' => false,
);
}
public function getName()
{
return 'tabs';
}
public function getParent(array $options)
{
return 'field';
}
private function findTabs(Form $form)
{
$prefix = $form->getName();
$tabs = array();
foreach ($form->getChildren() as $child)
{
foreach ($child->getTypes() as $type)
/* #var $child \Symfony\Component\Form\Form */
{
if (get_class($type) == __NAMESPACE__ . '\TabsType')
{
if ($child->getAttribute('starting'))
{
$tabs[$prefix . '_' . $child->getName()] = $child->getAttribute('label');
}
}
}
}
return $tabs;
}
}
?>
and Twig
{# \src\Alden\xyzBundle\Resources\views\Form\fields.html.twig #}
{% block tabs_row %}
{% if header %}
<ul>
{% for tid, t in tabs %}
<li>
{{ t }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if ending %}
</div>
{% endif %}
{% if starting %}
<div id="{{ id }}">
{% endif %}
{% endblock %}
and usage in form builder:
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('tabs_head', new TabsType(), array(
'ending' => false,
'starting' => false,
'header' => true
))
->add('tab_1', new TabsType(), array(
'ending' => false,
'label' => 'Podstawowe'
))
->add('firstName', null, array(
'label' => 'Imię'
))
->add('lastName', null, array(
'label' => 'Nazwisko'
))
->add('tab_contact', new TabsType(), array(
'label' => 'Kontakt'
))
->add('address', new AddressType(), array(
'label' => 'Adres zameldowania'
))
->add('tabs_end', new TabsType(), array(
'starting' => false
))
;
}
If you want a form to act like a form wizard you could look at look at the multi-step form bundle
It's pretty nice, you can for example, define step one as filling in software details and then on step2, fill out version details. or whatever you want.
Features
navigation (next, back, start over)
step descriptions
skipping of specified steps
different validation group for each step
dynamic step navigation
And here is a live demo
But in twig template I'd like to use single command
Do you mean to render the fields?
{{ form_rest(form) }}
renders all unrendered forms