Symfony flashbag get multiple time - symfony

In my app I work with flashbag in multiple place. To know if I need to display it to a place or another place I use an associative array as message :
$this->container->get('session')->getFlashBag()->add("info", ["category" => "mycat", "message" => "mymessage"]);
And in my twig :
{% for type in ['info', 'success', 'warning', 'danger'] %}
{% for message in app.session.flashbag.get(type) %}
{% if message.category is defined and message.category == "mycat" %}
<div class="col-md-12 alert alert-{{ type }}">
{{ message.message|raw }}
</div>
{% endif %}
{% endfor %}
{% endfor %}
It works fine, but I got an issue when I have this code in multiple place. The first time app.session.flashbag.get(type) is called it remove my messages from flashbag and the second time I call it the flashbag is empty...
Is there a way to put my message again in my flashbag if I do not display it ? Or to force it to keep in my flashbag until I manualy remove it ?
Edit:
I tried with app.session.flashbag.peek(type) and it works fine. But now my message is never removed from my flashbag is display on every page load.
Edit2:
I got this way :
{% for type in ['info', 'success', 'warning', 'danger'] %}
{% for message in app.session.flashbag.get(type) %}
{% if message.category is defined and message.category == "mycat" %}
<div class="col-md-12 alert alert-{{ type }}">
{{ message.message|raw }}
</div>
{% else %}
{{ app.session.flashbag.add(type, message) }}
{% endif %}
{% endfor %}
{% endfor %}
But I find it a little dirty. It there a better way to do it ?

Type does not have to be info, success, warning, danger. You can use type to store your categories, this would mean you can then get and clear ONLY the messages you want based on category, then store the what you are currently calling type as something else, i.e class.
Here is a useful list of methods you may not know about:
https://symfony.com/doc/current/components/http_foundation/sessions.html#flash-messages
Once you use your category as the type instead you simply can do get('mycat') as needed, ones that do not match will not be cleared
$this->container->get('session')->getFlashBag()->add("mycat", ["class" => "info", "message" => "mymessage"]);
{% for message in app.session.flashbag.get("mycat") %}
<div class="col-md-12 alert alert-{{ message.class }}">
{{ message.message|raw }}
</div>
{% endfor %}
Sometimes we get stuck on a certain path and then the way becomes distorted, I think sometimes we can forget that 'type' does not have to be the class thing despite how commonly it is used as that

Related

How to show symfony validation errors?

I try to visualize my error messages from the validator.
When i write the following example:-
{{ form_errors(form.pGWeek) }}
it works fine and i get the message. But my form has 200 fields and so it's not practical.
So i want to iterate over an Array with all messages, at the end of the form like this:
{% if form.name.vars.errors|length > 0 %}
<ul class="form-errors name">
{% for error in form.name.vars.errors %}
{{ error }}
{% endfor %}
</ul>
{% endif %}
But i did not get some messages. As well i tried some another versions.. but nothing worked. I'm using Symfony 2.7.
Can give me somebody a tip?
Thanks for a short feedback.
So you just want to show all errors for all children of a form without displaying them beside each input field?
Then you could iterate over all the children of your form, check if any errors appeared on this children and if so, iterate over all errors of this children. That could be something like that:
{% for children in form.children %}
{% if children.vars.errors is defined %}
{% for error in children.vars.errors %}
{#{{ dump(children) }}#}
{#{{ dump(error) }}#}
{{ dump(children.vars.name ~ ': ' ~ error.message) }}
{% endfor %}
{% endif %}
{% endfor %}
Which result in an error like description: This value should not be blank..
You can make that loop and condition in your controller using dependency injector, and then in your view you iterate that array of errors and put it into a div o wherever you want, I guess there is something like this:
$errors = $this->get('validator')->validate(yourObject);
if (!empty($errors)) {
foreach ($errors as $error)
throw new \Exception($error->getMessage());
}
That is if you want to stop the process and get an Exception, but you want to show that errors, then you make something similar, you only needs to delete that foreach and render your twig template and give it the $errors variable, in your template then you only need to make a for loop for get the errors!

Changing form data after failed validation in Symfony2

How can I change view data after failed validation (I want to change a checkbox value while displaying validation errors to explain why you can't select it). I suppose form events doesn't help here as the validation happens in the end.
The following code snippet from the Symfony's from bootstrap layout shows how you can check whether form field is valid or not:
{% block form_row -%}
{% spaceless %}
<div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{ form_label(form) }}
<div class="{{ block('form_group_class') }}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{%- endblock form_row %}
Look at if (not compound or force_error|default(false)) and not valid you can use same check in your code.
In short, you should add boolean value to your model, add checkbox type to your FormType representing this value and set it to false when validation fails, like this:
$form->setData($model);
if (!$form->isValid()) {
$model->setFlag(false);
} else {
// save model, redirect etc
}

Symfony 2: "File" field value is always empty

I'm attempting to make a form that builds a slider. It can have any number of images and I'd like to show a preview of already-uploaded images. Getting the multiple image field set up was easy enough, but I'm getting caught up on showing a preview of the image.
I'm using this template to render the "Slider Image" field:
{% block form_widget_simple %}
{% spaceless %}
<div class="form-widget slider">
{% set type = type|default('text') %}
{% if type == 'file' and value is not empty %}
<img src="{{ value }}" width="200"/><br/>
{% endif %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
</div>
{% endspaceless %}
{% endblock form_widget_simple %}
The value variable is always empty on file input types, so I'm not sure how I can get at the url of the uploaded images. I am using a custom field type that simply adds a file field and hooks up the data source (which is just a simple wrapper around Symfony\Component\HttpFoundation\File\File). If you need this code let me know, but its all boilerplate stuff so I doubt you do.
Thanks in advance.
Symfony2 FileType doesn't have value, its owerwritten in buildView method.
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FileType.php#L28
But you access it via forms.vars.data
{% if type == 'file' and form.vars.data is not null %}
{# Here you should somehow generate url to file but let assume
you have it in "/path/to/file/" folder that is accessible through web server #}
<img src="/path/to/file/{{ form.vars.data.name }}" /><br/>
{% endif %}

Symfony Twig customize an Individual field for a collection

In the documentation there is a way in Symfony to customize a Individual field, based on the name/id of the widget.
{% form_theme form _self %}
{% block _product_name_widget %}
<div class="text_widget">
{{ block('field_widget') }}
</div>
{% endblock %}
{{ form_widget(form.name) }}
Here, the _product_name_widget fragment defines the template to use for the field whose id is product_name (and name is product[name]).
This works for a normal widget, but not if a widget is inside a collection. Because of the extra columns. Like this:
name="productbundle_product_type[foobar][1][value]" id="productbundle_product_type_foobar_1_value"
What is the way to make the Twig customization work inside the collection?
I thought something like this, but that doesn't work:
{% for db in edit_form.list %}
{% block _productbundle_product_type_foobar_{{ db.name }}_widget %}
<div class="text_widget">
{{ block('field_widget') }}
</div>
{% endblock %}
{% endfor %}
Even the following doesn't work:
{% _productbundle_product_type_foobar_1_value_widget %}
What is the way to make it work?
I was working on a project a couple of evenings ago and encountered precisely this problem - the solution I found is a pair of blocks that look like this (stripped of project-specific code):
{# Collection markup #}
{% block my_collection_widget %}
{# Customise collection row prototype for allow_add #}
{% if prototype is defined %}
{% set data_prototype = block('my_collection_item_widget') %}
<div id="my_prototype" data-prototype="{{ data_prototype }}" style="display: none"></div>
{% endif %}
{% for prototype in form %}
{{ block('my_collection_item_widget') }}
{% endfor %}
{% endblock my_collection_widget %}
{# Collection row markup #}
{% block my_collection_item_widget %}
{# Collection contains simple, single type #}
{{ form_errors(prototype) }}
{{ form_label(prototype) }}
{{ form_widget(prototype) }}
{# OR #}
{# Collection contains a compound type, render child types independantly #}
{{ form_errors(prototype.inner_widget) }}
{{ form_label(prototype.inner_widget) }}
{{ form_widget(prototype.inner_widget) }}
{% endblock my_collection_item_widget %}
I know this is old question, but maybe people still happening upon this. This is explained fragment naming for collections.
You use _entry_ in these cases in place of the collection element number. Use the instructions in the link for fragment naming, but this might vary. Sometimes 'type' is part of the fragment's name, sometimes first letter is upper case, sometimes lower case, etc. I would use a browser developer tool to find the actual name to make sure. You might also be able to customize the names used by adding the getBlockPrefix function to the form class.
Therefore, in your case above, the customized block might look something like:
{% block _ProductBundle_product_entry_widget %}
<div> {{ form_row(form.field)}} </div>
{% endblock %}
Where 'field' would be the name of a field in the element of your collection.
A few things here:
1. Documentation Error
The docs seem to be a bit off with CamelCase entities. As for 5.3 it should be: _taskManager_taskLists_entry_widget (instead of _task_manager_task_lists_entry_widget)
You can confirm the naming by dumping the form or form field: {{ dump(form) }} or {{ dump(form.yourField) }} in your template and then look for unique_block_prefix within the vars section.
2. Do NOT use the block overrides, macros are much more easy
It makes things absolutely more complicated than necessary. Simply define a macro:
{% import _self as formMacros %}
{% macro formatCollection(form) %}
<div class="form-group row">
<div>
{{ form_widget(form.field1) }}
{{ form_widget(form.field2) }}
{{ form_widget(form.field3) }}
</div>
</div>
{% endmacro %}
then simply run it on each collection:
{% for item in form.collectionItems %}
{{ formMacros.formatCollection(item) }}
{% endfor %}
3. Prototype
Get the prototype before rendering the collection field. You can store it in a variable.
{% set prototype = form.collection.vars.prototype %}
Then simply render it whenever you like using our macro:
<div data-js="collection" data-prototype="{{ formMacros.formatCollection(prototype)|e('html_attr') }}">

How are labels on ArrayCollections in forms rendered?

I have a Customer object, which has many Emails.
I'm building a form for my customer, and I've added his emails as a collection. In my template, I render the emails portion like this:
<h4>Emails</h4>
{% for email in form.emails %}
<li>
{{ form_row(email.addr) }}
{{ form_row(email.isPrimary) }}
</li>
{% endfor %}
...
{{ form_rest(form) }}
This works fine, except if the customer has no emails. Then, form_rest() renders the label 'emails' at the bottom of my template.
Why does this only get rendered when form.emails is empty? How can I customize it? (Note I've already customized my label rendering for other form elements, and I don't want it to be the same for these 'collection labels'.)
I usually solved this problem this way:
{% for email in form.emails %}
{# ... #}
{% else %}
{{ form_widget(form.emails) }}
{% endfor %}
Unless someone suggests a better way of doing this.

Resources