I am "transplanting" my old website code into the new Symfony2.6. I have a Controller to render a form and create posts as in a blog. Then it is submited (POST method) in a second controller once it is valid. When I call the route to initialize the controller, for some reason I ignore, it also calls the ShowAction($slug), and it fails since the post it is not yet created and hence it does not have any $slug parameter. Why is it calling this showAction? Despite I type the url to just display the form.
Here is the error log at the line that starts the failure:
1. in src/Blog/BlogBundle/Services/PostManager.php at line 80
2. at PostManager ->findBySlug ('create_post') in src/Blog/BlogBundle/Controller PostController.php at line 55
3. at PostController ->ShowAction ('create_post')
4. at call_user_func_array (array(object(PostController), 'ShowAction'), array('create_post')) in app/bootstrap.php.cache at line 3020
I do not want to call the ShowAction.
Here is the controller code:
/**
* Show a Post
*
* #param string $slug
*
* #throws NotFoundHttpException
* #return array
*
* #Route("/{slug}")
* #Template()
*/
public function ShowAction($slug)
{
$post = $this->getPostManager()->findBySlug($slug);
$form_comment = $this->createForm(new CommentType());
return array(
'post' => $post,
'form_comment' => $form_comment->createView()
);
}
/**
* Displays a form to create a new Post entity.
*
* #Route("/new_post", name="_blog_backend_post_new")
* #Template()
*/
public function newAction()
{
$form = $this->createForm(new PostType(), new PostModel(new Post()));
return array(
'form' => $form->createView(),
);
}
/**
* Creates a new Post entity.
*
* #Route("/create_post", name="_blog_backend_post_create")
* #Method("POST")
* #Template("BlogBundle:Backend/Post:new.html.twig")
*/
public function createAction()
{
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new PostType(), new PostModel(new Post()));
$formHandler = new PostHandler($form, $request, new Post(), $em);
if ($formHandler->process()) {
return $this->redirect($this->generateUrl('_blog_backend_post'));
}
return array(
'form' => $form->createView(),
);
}
I do not know if its necessary but here are the involved templates:
* #Template("BlogBundle:Backend/Post:new.html.twig"):
{% extends "::base.html.twig" %}
{% block content %}
<form class="well" action="{{ url('_blog_backend_post_create') }}" method="post" {{ form_enctype(form) }}>
{% include 'BlogBundle:Backend/Post:edit.form.html.twig' with {'form': form } %}
</form>
{% endblock %}
BlogBundle:Backend/Post:edit.form.html.twig:
{% if form_errors(form) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form) }}
</div>
{% endif %}
<p>
<label for="">Title</label>
{% if form_errors(form.title) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form.title) }}
</div>
{% endif %}
{{ form_widget(form.title, { 'attr': {'class': 'w100'} }) }}
</p>
<p>
<label for="">Body</label>
{% if form_errors(form.body) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form.body) }}
</div>
{% endif %}
{{ form_widget(form.body, { 'attr': {'class': 'w100'} }) }}
</p>
<p>
<button class="btn" type="submit">Save</button>
<a class="btn" href="{{ url('_blog_backend_post') }}">Cancel</a>
</p>
{{ form_rest(form) }}
What I am missing and how can I correct it? Thank you in advanced.
The router matches the first route possible, in this case "/{slug}" where slug="create_post".
There are at least two solutions, where the first is the easiest, the second is the one I recommend:
Cut/Paste your showAction to the bottom of your file, in that case router will match /create_post to the createAction first
You can exclude this like: #Route("/{slug}", requirements={"slug" = "^(?<!create_post).+"})
The problem is that you are posting to /create_post which in turn matches the route /{slug}. slug is being set to 'create_post'. With the Symfony 2 router the first match wins and thus your showAction method is called.
Consider setting your ShowAction path to: '/show/{slug}'.
Or you could move your ShowAction down to the bottom of your controller file. That is a bit dangerous because you might forget and add a different action later. But either way will work.
Related
I try to insert images with relations OneToMany, but I get an error.
Expected argument of type "App\Entity\CategoryImage", "array" given at property path "images".
I have entities Category and CategoryImage:
/**
* #ORM\Entity(repositoryClass="App\Repository\CategoryRepository")
*/
class Category
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\CategoryImage", mappedBy="category", orphanRemoval=true ,cascade={"persist", "remove"})
*/
private $images;
.......
}
/**
* #ORM\Entity(repositoryClass="App\Repository\CategoryImageRepository")
*/
class CategoryImage
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $path;
/** #var \SplFileInfo */
protected $file;
....
}
Two forms:
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$data = $builder->getForm()->getData();
$builder
->add('name')
->add('parent',OptionType::class,[
'class'=>Category::class,
'selectType'=>'flatexcept',
'required'=>false,
])
->add('images',CollectionType::class,[
'entry_type' => ImageTypeMultiple::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
]);
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Category::class,
]);
}
}
class ImageTypeMultiple extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options )
{
$builder->add('file',FileType::class,[
'label'=>false,
'required'=>false,
'constraints' => [
new Image([
'maxSize' => '5M'
])
]
]);
}
public function getBlockPrefix()
{
return 'image_multiple';
}
}
My custom ImageTypeMultiple form_theme widget:
{% block image_multiple_widget -%}
{% spaceless %}
{% if form.vars.value.path|default(null) is null %}
<label for="{{ form.file.vars.id }}" class="image-upload btn btn-secondary btn-block"><i class="fa-cloud fa icon"></i> {{ 'select'|trans }}</label>
{% else %}
<img src="{{ form.vars.value.path|imagine_filter('sylius_small') }}" />
<label for="{{ form.file.vars.id }}" class="image-upload btn btn-secondary btn-block"><i class="fa-cloud fa icon"></i> {{ 'replace'|trans }}</label>
{% endif %}
<div class="d-none">
{{ form_widget(form.file) }}
</div>
<div class="element">
{{- form_errors(form.file) -}}
</div>
{% endspaceless %}
{% endblock %}
{% block collection_widget -%}
{% import _self as self %}
{% set attr = attr|merge({'class': attr.class|default ~ ' controls collection-widget'}) %}
{% spaceless %}
<div data-form-type="collection" {{ block('widget_container_attributes') }}
{% if prototype is defined and allow_add %}
data-prototype='{{ self.collection_item(prototype, allow_delete, 'Delete', '__name__')|e }}'
{%- endif -%}
>
{{ form_errors(form) }}
{% if prototypes|default is iterable %}
{% for key, subPrototype in prototypes %}
<input type="hidden" data-form-prototype="{{ key }}" value="{{ self.collection_item(subPrototype, allow_delete, button_delete_label, '__name__')|e }}" />
{% endfor %}
{% endif %}
<div data-form-collection="list" class="row">
{% for child in form %}
{{ self.collection_item(child, allow_delete, 'Delete', loop.index0) }}
{% endfor %}
</div>
{% if prototype is defined and allow_add %}
<a href="#" class="btn btn-primary" data-form-collection="add">
Add
</a>
{% endif %}
</div>
{% endspaceless %}
{%- endblock collection_widget %}
{% macro collection_item(form, allow_delete, button_delete_label, index) %}
{% spaceless %}
<div data-form-collection="item" data-form-collection-index="{{ index }}" class='col-lg-2 col-md-4 col-sm-6 imagetype_widget ml-3 mb-3' >
{{ form_widget(form) }}
{% if allow_delete %}
<a href="#" data-form-collection="delete" class="btn btn-warning btn-block">
<i class="fa fa-trash "></i>
Delete
</a>
{% endif %}
</div>
{% endspaceless %}
{% endmacro %}
My controller:
/**
* #Route("/{id}/edit", name="category_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Category $category): Response
{
$form = $this->createForm(CategoryType::class, $category);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
...}
}
I can open my edit page without error:
but after I select an image under images field and submit, it shows this error:
It shows
Expected argument of type "App\Entity\CategoryImage", "array" given at property path "images".
on the line $form->handleRequest($request) in the controller ,something goes wrong, I have been dealing with this problem for days, but cannot solve it.
I'm stuck with the following problem.
I'm using Symfony 4.2.3 to build a forum ( a piece of a bigger project )
I've made a ForumController who handle :
Forum Home page ( show forumCategories with their forumSubCategories )
Forum Category page ( show selected forumCategory with her forumSubCategories )
Forum Sub Category page ( show selected forumSubCategory with her forumTopics )
Forum Topic page ( show selected forumTopic with her forumMessage( i.e. reaction ) )
What i want is to implement an edit button who not redirect to an edit page but $(this).slidedown an edit form in a div. This button is display only if the ForumMessage author is the current login user.
So if this user have reply many time to the topic, I need just as many buttons ( and form ). After click on edit, the page can be reload and entity update.
For create / edit with redirection i've understood how to.
The problem was, how to handle an unknown number of edit MessageType form ( and so unknown form id ).
I've try to create an array of ForumMessage link to an array of MessageType Form.
But when i have to give the .createView() of each form to twig, my brain glitch.
So please, how can i have many edit form on sigle page( each link to the refered entity ) and handle them in my controller to .flush modification ?
I already implement JS function for the diplaying of the button and linked div.
The showTopic method of my ForumController.php :
/**
* #Route("/forum/category={idCategory}/subCategory={idSubCategory}/topic={idTopic}", name="topic")
* #ParamConverter("topic", options={"id" = "idTopic"})
* #param $idCategory
* #param $idSubCategory
* #param $idTopic
* #param Request $request
* #param ObjectManager $manager
* #param UserInterface $user
* #return \Symfony\Component\HttpFoundation\Response
* #throws \Exception
*/
public function showTopic($idCategory, $idSubCategory, $idTopic, Request $request, ObjectManager $manager, UserInterface $user = null) {
$topic = $this->getDoctrine()->getRepository(ForumTopic::class)->find($idTopic);
$userMessages = $this->getDoctrine()->getRepository(ForumMessage::class)->findBy([
'author' => "Kaarie",
'forumTopic' => $topic
]);
// Nouveau message sur un topic
$message = new ForumMessage();
$form = $this->createForm(ForumMessageType::class, $message);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$message->setAuthor($user->getUsername())
->setCreatedAt(new \DateTime())
->setForumTopic($topic);
$manager->persist($message);
$manager->flush();
return $this->redirectToRoute('topic', [
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'idTopic' => $topic->getId(),
]);
}
// Editer un message
$editMessage = new ForumMessage();
$editForm = $this->createForm(ForumMessageType::class, $editMessage);
$editForm->handleRequest($request);
if($editForm->isSubmitted() && $editForm->isValid()) {
$manager->persist($editMessage);
$manager->flush();
return $this->redirectToRoute('topic', [
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'idTopic' => $topic->getId(),
]);
}
return $this->render('forum/showTopic.html.twig',[
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'topic' => $topic,
'messageForm' => $form->createView(),
'editForm' => $editForm->createView(),
'userMessage' => $userMessages,
]);
}
The class MessageType in MessageType.php
class ForumMessageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
->add('submit', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ForumMessage::class,
]);
}
}
The twig part who display Message from showTopic.html.twig
<ul>
{% for message in topic.ForumMessages %}
<li>
{{ message.author }},</br>
{{ message.content }}
{% if app.user %}
{% if is_granted("ROLE_MODERATOR") %}
<button>Moderate</button> {# TODO: moderation d'un message #}
{% endif %}
{% if app.user.username == message.author %}
<div class="alert alert-danger" style="margin: 1em; display: none">
<h3>Etidé votre réponse :</h3>
{{ form_start(editForm) }}
{{ form_row(editForm.content) }}
{{ form_row(editForm.submit, {'label': 'Editer'}) }}
{#<button type="submit" class="btn btn-primary">Editer</button>#}
{{ form_end(editForm) }}
</div>
<button id="buton_EditTopic">Modifier</button>
{% endif %}
{% endif %}
</li>
{% endfor %}
</ul>
For any other ressources please ask me !
My approach would be (maybe some tweaking is necessary, didnt test it)
Short Hand explanation:
The list will only contain containers. When you edit one message, you load only the form and push that to this specific container. If you edit this and press save, it would send the form per ajax request to the controller. If the form is valid, it will return then a json repsonse instead of html ...
Controller:
/**
* #Route("/forum/category={idCategory}/subCategory={idSubCategory}/topic={idTopic}", name="topic")
* #ParamConverter("topic", options={"id" = "idTopic"})
* #param $idCategory
* #param $idSubCategory
* #param $idTopic
* #param Request $request
* #return \Symfony\Component\HttpFoundation\Response
* #throws \Exception
*/
public function showTopic(
$idCategory,
$idSubCategory,
$idTopic,
Request $request,
ObjectManager $manager,
UserInterface $user = null
)
{
$topic = $this->getDoctrine()->getRepository(ForumTopic::class)->find($idTopic);
$userMessages = $this->getDoctrine()->getRepository(ForumMessage::class)->findBy([
'author' => "Kaarie",
'forumTopic' => $topic
]);
return $this->render('forum/showTopic.html.twig',[
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'topic' => $topic,
'userMessage' => $userMessages,
]);
}
/**
* With this, you can create and mod Topics
* #Route("/forum/messages/{forumMessage}/mod-message", name="message.mod", defaults={"forumMessage":0})
* #IsGranted("ROLE_USER")
* #param Request $request
* #param ForumMessage $forumMessage
* #return mixed
*/
public function modTopic(
Request $request,
Objectmanager $manager,
ForumMessage $forumMessage=null
)
{
if($formMessage == null) {
$forumMessage = new ForumMessage();
/* set Additional Info here, maybe User, IP Adress or whatever */
}
$editForm = $this->createForm(ForumMessageType::class, $forumMessage);
$editForm->handleRequest($request);
if($editForm->isSubmitted() && $editForm->isValid()) {
$manager->persist($forumMessage);
$manager->flush();
return new JsonRepsonse(['status'=>true, 'message' => "ForumMessage save successfull"]);
}
return $this->render('mod.message.html.twig',[
'messageForm' => $editForm->createView(),
]);
}
FormType:
class ForumMessageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ForumMessage::class,
]);
}
}
list.html.twig
<ul>
{% for message in topic.ForumMessages %}
<li>
{{ message.author }},</br>
{{ message.content }}
{% if app.user %}
{% if is_granted("ROLE_MODERATOR") %}
<button>Moderate</button> {# TODO: moderation d'un message #}
{% endif %}
{% if app.user.username == message.author %}
<div id="modMessageContainer{{ message.id }}" class="alert alert-danger" style="margin: 1em; display: none">
</div>
<button onclick="modMessage(this);"
data-attr-url="{{ path('message.mod'.{'forumMessage':message.id}) }}"
data-attr-container="#modMessageContainer{{ message.id }}"
>Modifier</button>
{% endif %}
{% endif %}
</li>
{% endfor %}
</ul>
<script>
function modMessage(element)
{
$.ajax({
url: $(element).attr('data-attr-url'),
success: function(data) {
$($(element).attr('data-attr-container')).html(data).show();
}
});
}
function saveMessage(element)
{
var container = $(element).attr('data-attr-container');
$.ajax({
url: $(element).attr('data-attr-url'),
type:'POST',
data: $(container +' form').serialize(),
success: function(data) {
if(typeof data == 'object' && data instanceof Object && !(data instanceof Array)) {
if(data.status) {
location.reload()
} else {
alert(data.message);
}
} else {
$(container).show();
$('#modMessage').replaceWith($(data).find('#modMessage'));
}
}
});
}
</script>
mod.html.twig
<div>
<div id="modMessage">
<h3>Etidé votre réponse :</h3>
{{ form_start(editForm) }}
{{ form_row(editForm.content) }}
{{ form_row(editForm.submit, {'label': 'Editer'}) }}
{#<button type="submit" class="btn btn-primary">Editer</button>#}
{{ form_end(editForm) }}
<div style="text-align:right">
<button onclick="saveMessage(this);"
type="button"
class="btn btn-success"
data-attr-container="modMessageContainer{{ message.id }}"
data-attr-url="{{ path('message.mod', {'forumMessage':message.id}) }}"
>Save</button>
</div>
</div>
</div>
I have a form that is repeated over several different modules.
I would like to avoid repeating the same process over and over again and want to render the form once (in my application module) and then simply use a partial view script to call it wherever I wish like this;
<?php echo $this->partial('partialLayout/advertForm'); ?>
Following the advice here it would be pretty straightforward to do this if I simply created a HTML form in the partial script and then called it.
However, I want to use Zend Forms. Bearing in mind that zend form are loaded onto the page via the controller- how do I get around this.
i guess that the crux of the question is - how do you call a Zend Form in another module using a partial view script.
I did something like that with a ViewHelper. My solution is posted here, in Zend Forums.
There it is.
The code ViewHelper :
<?php
/**
* View helper to return the HTML code of the login form.
*
* #filesource LoginForm.php
* #encodage UTF-8
* #author DAFAP Informatique - Alain Pomirol
* #date 10 nov. 2015
* #version 2015-1
*/
namespace Login\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\View\Model\ViewModel;
use Login\Form\LoginForm as Formulaire;
class LoginForm extends AbstractHelper implements ServiceLocatorAwareInterface
{
protected $sm;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->sm = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->sm;
}
public function __invoke()
{
$form = new Formulaire();
$layout = new ViewModel(array(
'form' => $form
));
$viewRender = $this->getServiceLocator()->getServiceLocator()->get('ViewRenderer');
$layout->setTemplate('login/index/index.phtml');
return $viewRender->render($layout);
}
}
The statement in module.config.php in Login module :
'view_helpers' => array(
'invokables' => array(
'loginForm' => 'Login\View\Helper\LoginForm'
)
),
Use in view of the application module :
'view_helpers' => array(
'invokables' => array(
'loginForm' => 'Login\View\Helper\LoginForm'
)
),
It's a common problem! Forms can really be a slow-down with ZF2.
I use Twig (recommended) but you can port this approach to plain old ZF2 templates too.
Here's one such template I use nearly everywhere:
horizontal_form.twig
{# render rows only, not the form tag #}
{% do form.prepare() %}
{% for f in form %}
{% do f.setOption( 'twb-layout', 'horizontal' ) %}
{{ formRow( f ) }}
{% endfor %}
I've got a second with a slightly different approach to support a specific IdentifiableForm in my code:
identifiable_form.twig
<form id="{{ form.getAttribute('name') }}" name="{{ form.getAttribute('name') }}" action="{{ form.getAttribute('action') }}" class="form-horizontal" role="form">
<input type="hidden" name="class" value="{{ form.getAttribute('class') }}">
{% for f in form %}
{% do f.setOption( 'twb-layout', 'horizontal' ) %}
{% do f.setOption( 'twb-form-group-size', 'form-group-sm' ) %}
{{ formRow( f ) }}
{% endfor %}
<div class="hr-line-dashed"></div>
<button type="submit" class="btn btn-primary ladda-button" data-style="expand-right">Save</button>
</form>
With those two in the pocket, my usage looks like this:
In a twig template that should use a form
...include the template from the other:
<div class="panel-body">
{% set form = general_config_form %}
{% do form.prepare() %}
{% include 'application/generic/identifiable_form' %}
</div>
The controller action that operates that template looks like:
$vm = new ViewModel();
$vm->setTerminal( true );
$vm->setTemplate( 'application/admin/config/index' );
$sm = $this->getServiceLocator();
$fm = $sm->get( 'FormElementManager' );
$country = $config_mapper->get('general.country', true );
$general_config_form = $fm->get( GeneralConfigForm::class, [ 'country' => $country ] );
$general_config_form->setAttribute('action', '/admin-config/form-save' );
$vm->setVariable( 'general_config_form', $general_config_form );
In the end it's just a game of:
instantiating/loading the form in the action
setting the form as a variable in the ViewModel
including the "generic form template" from that action's actual template
On topic, if you hate rigging forms as much as I do, I made a little tool to save time here: https://github.com/Saeven/zf2-circlical-formtool
Second, if you use Bootstrap in your app, this form helper replacement really makes things look nice: https://github.com/neilime/zf2-twb-bundle
Good luck!
I created a form in Symfony like this:
$form = $this->createFormBuilder($template)
->add('product1', 'text')
->add('product2', 'text')
->add('save', 'submit')
->getForm();
Now this is my twig:
{{ form_start(form) }}
{% for i in 1..2 %}
<div class="col-md-3">
<div class="product">
<div class="name">
{{ form_label(form.product{{ i }} ) }}
{{ form_errors(form.product{{ i }} ) }}
{{ form_widget(form.product{{ i }} ) }}
</div>
</div>
</div>
{% endfor %}
{{ form_end(form) }
The main idea is iterate over the for and get a new form.product<X> each loop.
I can't make it works and I don't even know if it can be done in this way. Any idea?
I would recommend you to use Collection type for this purpose. But if you want do it your way you should do it this way:
{{ form_start(form) }}
{% for i in 1..2 %}
<div class="col-md-3">
<div class="product">
<div class="name">
{{ form_label( attribute(form, 'product' ~ i) ) }}
{{ form_errors( attribute(form, 'product' ~ i) ) }}
{{ form_widget( attribute(form, 'product' ~ i) ) }}
</div>
</div>
</div>
{% endfor %}
{{ form_end(form) }
You're right, it probably won't work. For information, concatenation symbol in Twig is "~".
In your case, if your entity is supposed to have 2 or more "products" you should use collections instead of creating manually each product.
In your entity you would have something like
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
And on the product entity, you would have
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
And then in your first entity __constructor or in your controller you iterate to create as many product as you want and you add them to the entity.
In your form, you would just have to add :
$builder->add('products', 'collection');
and you would be able to iterate on it in Twig.
Hopefully this will help you
You should start with a Entity with only a ManyToOne relation to the "product" entity. Lets say that we call this entity "ProductContainer".
Then you create an form for the ProductContainer with only one field with the type 'collection' which will make a list of products for you.
You can follow this tutorial: http://symfony.com/doc/current/cookbook/form/form_collections.html
I have this
{% block form_row %}
<div class="form-group">
{{ form_label(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
Its used to override the main Twig form fields.
But I need the label to be different depending on the type of form field that is being rendered.
How can I get that here and then call something else instead of form_label ?
I essentially want to be able to do this, this is because the label it would appear comes after the input for checkboxes, but I want to reverse this / customise it.
{% block form_row %}
<div class="form-group">
{% if(type is checkbox) %}
// checkbox label here
{% else %}
{{ form_label(form) }}
{% endif %}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
You can override the blocks that are used to render a specific form type.
For example, if you want to override the label template of an email input, you should override the email_label block:
{% block email_label %}
This is the template used for all email input
{% endblock %}
{% block form_label %}
This is the fallback template for all other input types
{% endblock %}
You can check which blocks you can override for a specific form view by looking into form.vars.block_prefixes.
For example, for a "personnal_email" field of type "email", it'll contain :
array:4 [▼
0 => "form"
1 => "text"
2 => "email"
3 => "_form_personnal_email"
]
which mean you can override blocks (starting with the less specific one) form_(widget|label|error), text_(widget|label|error), email_(widget|label|error) and _form_personnal_email_(widget|label|error) (the last one is usefull to override the rendering of a very specific field).
Does it answer your question?
UPDATE
here's what you have to do:
{% block form_row %}
<div class="form-group">
{{ form_label(form) }}
{{ form_widget(form) }}
</div>
{% endblock %}
{% block checkbox_label %}
<!-- Your checkbox specific label -->
{% endblock %}
You cannot access type in the form_row block as it's only defined in sub blocks of form_widget (see here for example)
You can use custom format:
<div class="form-group">
{{ form_label(form.your_value, 'Your Title of field', { 'label_attr': {'class': 'col-sm-3 control-label'} }) }}
<div class="col-sm-9">
{{ form_widget(form.your_value, { 'attr': {'class': 'form-control selectJS'} }) }}
</div>
</div>
or you can use FormType (if you generate entity, this is file in Form folder), like a:
<?php
namespace Ens\YourBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class NoticeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title','text', array(
'label'=>'Title',
'label_attr'=>array( 'class'=>'col-sm-3 control-label' ),
'attr'=>array( 'class'=> 'form-control')
))
->add('text', 'textarea', array(
'label'=>'Text',
'label_attr'=>array( 'class'=>'col-sm-3 control-label' ),
'attr'=>array( 'class'=> 'form-control')
))
->add('keep_on_top','checkbox', array(
'label'=>'Keep on top',
'required'=>false
))
->add('start', 'date', array(
'attr'=>array( 'class'=> 'hidden')
))
->add('end', 'date', array(
'attr'=>array( 'class'=> 'hidden')
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ens\YourBundle\Entity\Notice'
));
}
/**
* #return string
*/
public function getName()
{
return 'notice';
}
}