Based on @Jay Seth’s answer, I made a version of FormErrors class especially for Ajax Forms:
// src/AppBundle/Form/FormErrors.php
namespace AppBundleForm;
class FormErrors
{
/**
* @param SymfonyComponentFormForm $form
*
* @return array $errors
*/
public function getArray(SymfonyComponentFormForm $form)
{
return $this->getErrors($form, $form->getName());
}
/**
* @param SymfonyComponentFormForm $baseForm
* @param SymfonyComponentFormForm $baseFormName
*
* @return array $errors
*/
private function getErrors($baseForm, $baseFormName) {
$errors = array();
if ($baseForm instanceof SymfonyComponentFormForm) {
foreach($baseForm->getErrors() as $error) {
$errors[] = array(
"mess" => $error->getMessage(),
"key" => $baseFormName
);
}
foreach ($baseForm->all() as $key => $child) {
if(($child instanceof SymfonyComponentFormForm)) {
$cErrors = $this->getErrors($child, $baseFormName . "_" . $child->getName());
$errors = array_merge($errors, $cErrors);
}
}
}
return $errors;
}
}
Usage (e.g. in your action):
$errors = $this->get('form_errors')->getArray($form);
Symfony version: 2.8.4
Example JSON response:
{
"success": false,
"errors": [{
"mess": "error_message",
"key": "RegistrationForm_user_firstname"
}, {
"mess": "error_message",
"key": "RegistrationForm_user_lastname"
}, {
"mess": "error_message",
"key": "RegistrationForm_user_email"
}, {
"mess": "error_message",
"key": "RegistrationForm_user_zipCode"
}, {
"mess": "error_message",
"key": "RegistrationForm_user_password_password"
}, {
"mess": "error_message",
"key": "RegistrationForm_terms"
}, {
"mess": "error_message2",
"key": "RegistrationForm_terms"
}, {
"mess": "error_message",
"key": "RegistrationForm_marketing"
}, {
"mess": "error_message2",
"key": "RegistrationForm_marketing"
}]
}
The error object contains the «key» field, which is the id of the input DOM element, so you can easily populate error messages.
If you have child forms inside the parent, don’t forget to add the cascade_validation
option inside the parent form’s setDefaults
.
Первая часть
Вторая часть
7 Проверка и сообщения об ошибках
Большинство наших форм должны показывать сообщения проверки, чтобы информировать пользователя об ошибках, которые он сделал.
Thymeleaf предлагает несколько инструментов для этого: несколько функций в объекте #fields, атрибуты th:errors и th:errorclass.
7.1 Field errors
Давайте посмотрим, как мы можем установить конкретный класс CSS для поля, если оно содержит ошибку:
<input type="text" th:field="*{datePlanted}"
th:class="${#fields.hasErrors('datePlanted')}? fieldError" />
Как видите, функция #fields.hasErrors(…) получает выражение поля в качестве параметра (datePlanted) и возвращает логическое значение, указывающее, существуют ли какие-либо ошибки проверки для этого поля.
Мы также можем получить все ошибки для этого поля и повторить их:
<ul>
<li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />
</ul>
Вместо итерации мы могли бы также использовать th:errors, специализированный атрибут, который создает список со всеми ошибками для указанного селектора, разделенными <br />:
<input type="text" th:field="*{datePlanted}" />
<p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>
Упрощение стилей CSS на основе ошибок: th:errorclass
Пример, который мы видели выше, установка CSS-класса для input формы, если в этом поле есть ошибки, настолько распространена, что Thymeleaf предлагает специальный атрибут для точного выполнения: th:errorclass.
Примененный к тегу поля формы (input, select, textarea…), он будет считывать имя поля, которое нужно проверить, из любых существующих атрибутов name или th:field в том же теге, а затем добавлять указанный класс CSS к тегу, если такое поле имеет какие-либо связанные ошибки:
<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />
Если в datePlanted есть ошибки, это будет выглядеть так:
<input type="text" id="datePlanted" name="datePlanted" value="2013-01-01" class="small fieldError" />
7.2 Все ошибки
А что если мы хотим показать все ошибки в форме? Нам просто нужно запросить методы #fields.hasErrors(…) и #fields.errors(…) с константами ‘*‘ или ‘all‘ (которые эквивалентны):
<ul th:if="${#fields.hasErrors('*')}">
<li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
</ul>
Как и в приведенных выше примерах, мы могли бы получить все ошибки и итерировать по ним …
<ul>
<li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
</ul>
… а также создать разделенный <br /> список:
<p th:if="${#fields.hasErrors('all')}" th:errors="*{all}">Incorrect date</p>
Наконец, обратите внимание, что #fields.hasErrors(‘*’) эквивалентно #fields.hasAnyErrors(), и #fields.errors(‘*’) эквивалентно #fields.allErrors(). Используйте тот синтаксис, который вы предпочитаете:
<div th:if="${#fields.hasAnyErrors()}">
<p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
</div>
7.3 Глобальные ошибки
В форме Spring существует третий тип ошибок: глобальные ошибки. Это ошибки, которые не связаны с какими-либо конкретными полями в форме, но все еще существуют.
Thymeleaf предлагает константу global для доступа к этим ошибкам:
<ul th:if="${#fields.hasErrors('global')}">
<li th:each="err : ${#fields.errors('global')}" th:text="${err}">Input is incorrect</li>
</ul>
<p th:if="${#fields.hasErrors('global')}" th:errors="*{global}">Incorrect date</p>
… a также эквивалентные вспомогательные методы #fields.hasGlobalErrors() и #fields.globalErrors():
7.4 Отображение ошибок вне форм
Ошибки проверки формы также могут отображаться вне форм с помощью переменных (${…}) вместо выражений выбора (*{…}) и префикса имени компонента, поддерживающего форму:
<div th:errors="${myForm}">...</div>
<div th:errors="${myForm.date}">...</div>
<div th:errors="${myForm.*}">...</div>
<div th:if="${#fields.hasErrors('${myForm}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.date}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.*}')}">...</div>
<form th:object="${myForm}">
...
</form>
7.5 Богатые объекты ошибок
Thymeleaf предлагает возможность получения информации об ошибках формы в виде bean-компонентов (вместо простых строк) с атрибутами fieldName (String), message (String) и global (boolean).
Эти ошибки могут быть получены с помощью служебного метода #fields.detailedErrors():
<ul>
<li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
<span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
<span th:text="${e.message}">The error message</span>
</li>
</ul>
8 Это все еще прототип!
Наше приложение готово. Но давайте еще раз посмотрим на .html страницу, которую мы создали …
Одним из самых приятных последствий работы с Thymeleaf является то, что после всех этих функций, которые мы добавили в наш HTML, мы все равно можем использовать этот HTML в качестве прототипа (мы говорим, что это Natural Template). Давайте откроем seedstartermng.html прямо в нашем браузере, не запуская наше приложение:
Вот оно! Это не работающее приложение, это не реальные данные … но это совершенно правильный прототип, составленный из прекрасно отображаемого HTML-кода.
9 Служба конверсии (The Conversion Service)
9.1 Конфигурирование
Как объяснялось ранее, Thymeleaf может использовать Службу преобразования, зарегистрированную в контексте приложения. Наш класс конфигурации приложения, расширяя собственный помощник Spring WebMvcConfigurerAdapter, автоматически зарегистрирует такой сервис преобразования, который мы можем настроить, добавив необходимые средства форматирования. Давайте еще раз посмотрим, как это выглядит:
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
registry.addFormatter(dateFormatter());
}
@Bean
public VarietyFormatter varietyFormatter() {
return new VarietyFormatter();
}
@Bean
public DateFormatter dateFormatter() {
return new DateFormatter();
}
9.2 Синтаксис двойной скобки
Служба преобразования может быть легко применена для преобразования/форматирования любого объекта в строку. Это делается с помощью синтаксиса выражений в двойных скобках:
- Для переменных выражений: ${{…}}
- Для выражения выбора: *{{…}}
Так, например, учитывая преобразователь Integer-to-String, который добавляет запятые в качестве разделителя тысяч, это:
<p th:text="${val}">...</p>
<p th:text="${{val}}">...</p>
… должно привести к:
<p>1234567890</p>
<p>1,234,567,890</p>
9.3 Использование в формах
Ранее мы видели, что каждый атрибут th:field всегда будет применять сервис преобразования, так что это:
<input type="text" th:field="*{datePlanted}" />
… фактически эквивалентно:
<input type="text" th:field="*{{datePlanted}}" />
Обратите внимание, что согласно требованию Spring, это единственный сценарий, в котором служба преобразования применяется в выражениях с использованием синтаксиса с одной скобкой.
9.4 #conversions объект преобразования
Служебный объект преобразования #conversions позволяет вручную запускать службу преобразования там, где это необходимо:
<p th:text="${'Val: ' + #conversions.convert(val,'String')}">...</p>
Синтаксис для этого служебного объекта:
- #conversions.convert(Object, Class): преобразует объект в указанный класс
- #conversions.convert(Object, String): то же, что и выше, но с указанием целевого класса в виде String (обратите внимание, что пакет java.lang. может быть опущен)
10 Отрисовка фрагментов шаблона Template Fragments (AJAX etc)
Thymeleaf предлагает возможность визуализации только части шаблона в результате его выполнения: фрагмента.
Это может быть полезным инструментом компонентизации. Например, его можно использовать на контроллерах, которые выполняются при вызовах AJAX, которые могут возвращать фрагменты разметки страницы, которая уже загружена в браузер (для обновления выбора, включения/выключения кнопок…).
Фрагментарный рендеринг может быть достигнут с использованием спецификаций фрагментов Thymeleaf: объектов, реализующих интерфейс org.thymeleaf.fragment.IFragmentSpec.
Наиболее распространенной из этих реализаций является org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec, которая позволяет указывать фрагмент с помощью селектора DOM в точности так же, как те, что используются в th:include или th:replace.
10.1 Определение фрагментов в бине представления
Bean-компоненты представления — это bean-компоненты класса org.thymeleaf.spring4.view.ThymeleafView, объявленные в контексте приложения (аннотация Bean, если вы используете конфигурацию Java). Они позволяют задавать фрагменты следующим образом:
@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
view.setMarkupSelector("content");
return view;
}
Учитывая приведенное выше определение bean-компонента, если наш контроллер возвращает content-part (имя вышеупомянутого bean-компонента)…
@RequestMapping("/showContentPart")
public String showContentPart() {
...
return "content-part";
}
… Thymeleaf вернет только фрагмент content шаблона индекса — расположение которого, вероятно, будет примерно таким же, как /WEB-INF/templates/index.html, после применения префикса и суффикса. Таким образом, результат будет полностью эквивалентен указанию index :: content:
<!DOCTYPE html>
<html>
...
<body>
...
<div th:fragment="content">
Only this div will be rendered!
</div>
...
</body>
</html>
Также обратите внимание, что благодаря мощным селекторам разметки Thymeleaf мы можем выделять фрагмент в шаблоне без каких-либо атрибутов th:fragment. Давайте используем атрибут id, например:
@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
view.setMarkupSelector("#content");
return view;
}
10.2 Определение фрагментов в возвращаемом значении контроллера
Вместо декларирования view beans, фрагменты могут быть определены из контроллера с использованием синтаксиса выражений фрагментов (fragment expressions). Просто как в аттрибутах th:insert или th:replace.
@RequestMapping("/showContentPart")
public String showContentPart() {
...
return "index :: content";
}
Разумеется, снова доступна вся мощь селекторов DOM, поэтому мы можем выбрать наш фрагмент на основе стандартных атрибутов HTML, таких как id=«content»:
@RequestMapping("/showContentPart")
public String showContentPart() {
...
return "index :: #content";
}
И мы также можем использовать параметры, такие как:
@RequestMapping("/showContentPart")
public String showContentPart() {
...
return "index :: #content ('myvalue')";
}
11 Продвинутые интеграционные фичи
11.1 Интеграция с RequestDataValueProcessor
Thymeleaf легко интегрируется с интерфейсом Spring RequestDataValueProcessor. Этот интерфейс позволяет перехватывать URL-адреса ссылок, URL-адреса формы и значения полей формы до их записи в результат разметки, а также прозрачно добавлять скрытые поля формы, которые включают функции безопасности, например, такие как: защита против CSRF (подделка межсайтовых запросов).
Реализация RequestDataValueProcessor может быть легко настроена в контексте приложения. Он должен реализовать интерфейс org.springframework.web.servlet.support.RequestDataValueProcessor и иметь requestDataValueProcessor в качестве имени бина:
@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new MyRequestDataValueProcessor();
}
…и Thymeleaf будет использовать это следующим образом:
- th:href и th:src вызывают RequestDataValueProcessor.processUrl(…) перед отрисовкой URL
- th:action вызывает RequestDataValueProcessor.processAction(…) перед рендерингом атрибута action формы, и дополнительно он обнаруживает, когда этот атрибут применяется к тегу <form>, который в любом случае должен быть единственным местом, и в этом случае вызывает RequestDataValueProcessor.getExtraHiddenFields(…) и добавляет возвращенные скрытые поля непосредственно перед закрывающим тегом </form>
- th:value вызывает RequestDataValueProcessor.processFormFieldValue(…) для отрисовки значения, на которое оно ссылается, если только в том же теге нет th:field (в этом случае th:field сам позаботится)
- th:field вызывает RequestDataValueProcessor.processFormFieldValue(…) для отрисовки значения поля, к которому применяется (или тела тега, если им является <textarea>)
Обратите внимание, что существует очень мало сценариев, в которых вам нужно было бы явно реализовать RequestDataValueProcessor в вашем приложении. В большинстве случаев это будет автоматически использоваться библиотеками безопасности, которые вы прозрачно используете, например, например, Spring Security’s CSRF.
11.1 Построение URIs к контроллерам
Начиная с версии 4.1, Spring дает возможность создавать ссылки на аннотированные контроллеры непосредственно из представлений, без необходимости знать URI, на которые эти контроллеры отображаются.
В Thymeleaf это может быть достигнуто с помощью выражения #mvc.url(…), который позволяет задавать методы контроллера заглавными буквами класса контроллера, в котором они находятся, за которым следует имя метода. Это эквивалентно пользовательской функции spring:mvcUrlx(…) в JSP.
Например, для:
public class ExampleController {
@RequestMapping("/data")
public String getData(Model model) { ... return "template" }
@RequestMapping("/data")
public String getDataParam(@RequestParam String type) { ... return "template" }
}
Следующий код создаст ссылки на методы:
<a th:href="${(#mvc.url('EC#getData')).build()}">Get Data Param</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">Get Data Param</a>
Вы можето почитать об этом механизме по ссылке http://docs.spring.io/spring-framework/docs/4.1.2.RELEASE/spring-framework-reference/html/mvc.html#mvc-links-to-controllers-from-views
12 Spring WebFlow интеграция
Интеграционные пакеты Thymeleaf + Spring включают интеграцию с Spring WebFlow (2.3+).
WebFlow включает некоторые возможности AJAX для рендеринга фрагментов отображаемой страницы при срабатывании определенных событий (переходов), и чтобы Thymeleaf мог отслеживать эти запросы AJAX, нам нужно будет использовать другую реализацию ViewResolver, настроенную так:
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring4.view.AjaxThymeleafViewResolver">
<property name="viewClass" value="org.thymeleaf.spring4.view.FlowAjaxThymeleafView" />
<property name="templateEngine" ref="templateEngine" />
</bean>
… и тогда этот ViewResolver может быть сконфигурирован в WebFlow ViewFactoryCreator как:
<bean id="mvcViewFactoryCreator"
class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers" ref="thymeleafViewResolver"/>
</bean>
Отсюда вы можете определить шаблоны Thymeleaf в вашем view-state’s:
<view-state id="detail" view="bookingDetail">
...
</view-state>
В приведенном выше примере bookingDetail — это шаблон Thymeleaf, указанный обычным способом, понятный любому из резолверов шаблона, настроенных в TemplateEngine.
12.2 AJAX фрагменты в Spring WebFlow
Обратите внимание, что здесь объясняется только способ создания фрагментов AJAX для использования с Spring WebFlow. Если вы не используете WebFlow, создание контроллера Spring MVC, который отвечает на запрос AJAX и возвращает кусок HTML, так же просто, как и создание любого другого контроллера, возвращающего шаблон, с единственным исключением, что вы, вероятно, будете возвращать фрагмент наподобие «main :: admin» из вашего метода контроллера.
WebFlow позволяет определить отрисовку через AJAX с <render> тегов, например так:
<view-state id="detail" view="bookingDetail">
<transition on="updateData">
<render fragments="hoteldata"/>
</transition>
</view-state>
Эти фрагменты (в данном случае hoteldata) могут представлять собой разделенный запятыми список фрагментов, указанных в разметке с помощью th:fragment:
<div id="data" th:fragment="hoteldata">
This is a content to be changed
</div>
Всегда помните, что указанные фрагменты должны иметь атрибут id, чтобы библиотеки Spring JavaScript, работающие в браузере, могли заменить разметку.
Теги <render> также можно указывать с помощью селекторов DOM:
<view-state id=«detail» view=«bookingDetail»>
/>
</view-state>
…и это означает, что нет нужды в th:fragment:
<div id="data">
This is a content to be changed
</div>
Что касается кода, который запускает переход updateData, он выглядит следующим образом:
<script type="text/javascript" th:src="@{/resources/dojo/dojo.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring-Dojo.js}"></script>
...
<form id="triggerform" method="post" action="">
<input type="submit" id="doUpdate" name="_eventId_updateData" value="Update now!" />
</form>
<script type="text/javascript">
Spring.addDecoration(
new Spring.AjaxEventDecoration({formId:'triggerform',elementId:'doUpdate',event:'onclick'}));
</script>
I recently saw an old post on StackOverflow called
How to get form validation errors after binding the request to the form.
It has a lot of answers, most of them are only partially right and a lot
are outdated. So, I wanted to look at the right answer, and why it’s that
way :).
Tip
To see real examples of using forms and customizing form rendering, start
off with our Symfony2 Series (Episode 2 and Episode 4.
Debugging
First, if you’re trying to figure out what errors you have and which field
each is attached to, you should use the
Form::getErrorsAsString()
method that was introduced in Symfony 2.1 (so a long time ago!). Use it temporarily
in a controller to see what’s going on::
public function formAction(Request $request)
{
$form = $this->createForm(new ProductType());
$form->handleRequest($request);
if ($form->isValid()) {
// ... form handling
}
var_dump($form->getErrorsAsString());die;
$this->render(
'DemoBundle:Product:form.html.twig',
array('form' => $form->createView())
);
}
That’s it. To make things even simpler, you also have the Form panel of
the web debug toolbar in Symfony 2.4. So, debugging form errors
Why $form->getErrors() doesn’t Work
Tip
As of Symfony 2.5, we have a new tool! $form->getErrors(true)
will
return all of the errors from all of the fields on your form.
Raise your hand virtually if you’ve tried doing this to debug a form::
public function formAction(Request $request)
{
$form = $this->createForm(new ProductType());
$form->handleRequest($request);
if ($form->isValid()) {
// ... form handling
}
var_dump($form->getErrors());die;
$this->render(
'DemoBundle:Product:form.html.twig',
array('form' => $form->createView())
);
}
What do you get? Almost definitely an empty array, even when the form has
lots of errors. Yea, I’ve been there too.
The reason is that a form is actually more than just one Form
object:
it’s a tree of Form
objects. Each field is represented by its own Form
object and the errors for that field are stored on it.
Assuming the form has name
and price
fields, how can we get the errors
for each field?
$globalErrors = $form->getErrors()
$nameErrors = $form['name']->getErrors();
$priceErrors = $form['price']->getErrors();
By saying $form['name']
, we get the Form
object that represents just
the name
field, which gives us access to just the errors attached to
that field. This means that there’s no one spot to get all of the errors
on your entire form. Saying $form->getErrors()
gives you only «global»
errors, i.e. errors that aren’t attached to any specific field (e.g. a CSRF token
failure).
Render all the Errors at the Top of your Form
Tip
As of Symfony 2.5, you can use $form->getErrors(true)
to get an array
of all the errors in your form. Yay!
One common question is how you might render all of your errors in one big
list at the top of your form. Again, there’s no one spot where you can
get a big array of all of the errors, so you’d need to build it yourself::
// a simple, but incomplete (see below) way to collect all errors
$errors = array();
foreach ($form as $fieldName => $formField) {
// each field has an array of errors
$errors[$fieldName] = $formField->getErrors();
}
We can iterate over $form
(a Form
object) to get all of its fields.
And again, remember that each field ($formField
here), is also a Form
object, which is why we can call
Form::getErrors()
on each.
In reality, since a form can be many-levels deep, this solution isn’t good
enough. Fortunately, someone already posted a more complete one on
Stack Overflow (see the 2.1 version).
From here, you can pass these into your template and render each. Of course,
you’ll need to make sure that you don’t call {{ form_errors() }}
on any
of your fields, since you’re printing the errors manually (and remember that
form_row
calls form_errors
automatically).
Tip
Each field also has an error_bubbling option. If this is set to true
(it defaults to false
for most fields), then the error will «bubble»
and attach itself to the parent form. This means that if you set this
option to true
for every field, all errors would be attached to
the top-level Form object and could be rendered by calling {{ form_errors(form) }}
in Twig.
Accessing Errors Inside Twig
We can also do some magic in Twig with errors using magical things called
form variables. These guys are absolutely fundamental to customizing
how your forms render.
Tip
If you’re new to form theming and variables and need to master them,
check out Episode 4 of our Symfony series.
Normally, field errors are rendered in Twig by calling form_errors
on
each individual field:
{{ form_errors(form) }}
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
{{ form_errors(form.name) }}
Tip
The form_row
function calls form_errors
internally.
Just like in the controller, the errors are attached to the individual fields
themselves. Hopefully it make sense now why form_errors(form)
renders global
errors and form_errors(form.name)
renders the errors attached to the
name field.
Tip
Once you’re in Twig, each field (e.g. form
, form.name
) is an instance
of :symfonyclass:Symfony\Component\Form\FormView
.
If you need to customize how the errors are rendered, you can always use
form theming. But you can also do it by leverage form variables:
{{ form_errors(form) }}
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
{% if form.name.vars.errors|length > 0 %}
<ul class="form-errors name">
{% for error in form.name.vars.errors %}
{{ error }}
{% endfor %}
</ul>
{% endif %}
The key here is form.name.vars
: a strange array of «variables» that you
have access to on every field. One of the variables you have access to
is errors
, but there are many others, like label
and required
.
Each variable is normally used internally to render the field, but you can
use them manually if you need to:
<label for="form.name.vars.id">
{{ form.name.vars.label }} {{ form.name.vars.required ? '*' : '' }}
</label>
To find out what variables a field has, just dump them:
{{ dump(form.price.vars) }}
Now that you know about the magic form.vars
, you could also use it to render
all validation errors at the top of your form. This will only work for simple forms
(i.e. forms without nested fields), which most forms are:
{% set messages = [] %}
{% for child in form.children %}
{% for error in child.vars.errors %}
{% set messages = messages|merge([error.message]) %}
{% endfor %}
{% endfor %}
{% if messages|length %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
Tip
When you are form theming, these variables become accessible in your
form theme template as local variables inside the form blocks (e.g.
simply label
or id
).
Takeaways
The key lesson is this: each form is a big tree. The top level Form
has
children, each which is also a Form
object (or a FormView
object
when you’re in Twig). If you want to access information about a field (is
it required? what are its errors?), you need to first get access to the child
form and go from there.
Learn More
Stuck on other Symfony topics or want to learn Symfony from the context of
an actual project? Check out our Getting Started with Symfony Series and
cut down on your learning curve!
Возвращает массив со всеми значениями пользовательских полей для определенной записи, пользователя, комментария, термина и так далее.
Функция не очень элегантна и тратит много памяти PHP / SQL-запросов, если вы не используете все значения.
Хуков нет.
Возвращает
(Массив)
. Ассоциативный массив (field name => field value).
Использование
get_fields( $post_id, $format_value );
- $post_id(mixed)
- ID записи (пользователя, рубрики и .д.), для которой нужно получить метаполя.
По умолчанию: false (ID текущей записи в цикле) - $format_value(bool)
- Применять ли форматирование, указанное в настройках поля.
По умолчанию: true
Примеры
#1 Получим значения метаполей для текущей записи
Отобразим для текущей записи в виде списка все метаполя (имена полей и их значения).
$fields = get_fields(); if( $fields ): ?> <ul> <?php foreach( $fields as $name => $value ): ?> <li><b><?php echo $name; ?></b> <?php echo $value; ?></li> <?php endforeach; ?> </ul> <?php endif; ?>
#2 Получим значения для разных сущностей
// Получим значения всех метаполей текущей записи. $fields = get_fields(); // Получим значения всех метаполей записи с ID = 1. $post_fields = get_fields( 1 ); // Получим значения всех метаполей пользователя с ID = 2. $user_fields = get_fields( 'user_2' ); // Получим значения всех метаполей термина с ID = 3. $term_fields = get_fields( 'term_3' ); // Получим значения всех метаполей термина с таксономией category и ID = 3. $term_fields = get_fields( 'category_3' ); // Получим значения всех метаполей комментария с ID = 4. $comment_fields = get_fields( 'comment_4' ); // Получим значения всех метаполей всех страниц опций, созданных ACF. $option_fields = get_fields( 'options' ); // Или так. $option_fields = get_fields( 'option' );
#3 Получим значения метаполей для текущей записи без форматирования
В этом примере показано, как получить все поля (имя и значение) без какого-либо форматирования.
Форматирование относится к тому, как изменяются значения после загрузки из базы данных. Например, значение поля изображения сохраняется в базе данных как просто ID вложения, но может быть возвращено в виде URL-адреса, в зависимости от настроек поля.
В некоторых случаях может быть полезно убедиться, что возвращается только необработанное значение, независимо от настроек поля. Для этого мы используем параметр $format_value
.
$fields = get_fields( 123, false );
- All Known Subinterfaces:
BindingResult
- All Known Implementing Classes:
AbstractBindingResult
,AbstractErrors
,AbstractPropertyBindingResult
,BeanPropertyBindingResult
,BindException
,DirectFieldBindingResult
,EscapedErrors
,MapBindingResult
,MethodArgumentNotValidException
,WebExchangeBindException
public interface Errors
Stores and exposes information about data-binding and validation
errors for a specific object.
Field names can be properties of the target object (e.g. «name»
when binding to a customer object), or nested fields in case of
subobjects (e.g. «address.street»). Supports subtree navigation
via setNestedPath(String)
: for example, an
AddressValidator
validates «address», not being aware
that this is a subobject of customer.
Note: Errors
objects are single-threaded.
- Author:
- Rod Johnson, Juergen Hoeller
- See Also:
-
setNestedPath(java.lang.String)
BindException
DataBinder
ValidationUtils
-
Field Summary
Fields
The separator between path elements in a nested path,
for example in «customer.name» or «customer.address.street». -
Method Summary
void
Add all errors from the given
Errors
instance to this
Errors
instance.Get all errors, both global and field ones.
int
Return the total number of errors.
Get the first error associated with a field, if any.
Get the first error associated with the given field, if any.
int
Return the number of errors associated with a field.
int
Return the number of errors associated with the given field.
Get all errors associated with a field.
Get all errors associated with the given field.
Return the type of a given field.
Return the current value of the given field, either the current
bean property value or a rejected update from the last binding.Get the first global error, if any.
int
Return the number of global errors.
Return the current nested path of this
Errors
object.Return the name of the bound root object.
boolean
hasErrors()
Return if there were any errors.
boolean
Are there any field errors?
boolean
Are there any errors associated with the given field?
boolean
Are there any global errors?
void
Pop the former nested path from the nested path stack.
void
Push the given sub path onto the nested path stack.
void
Register a global error for the entire target object,
using the given error description.void
Register a global error for the entire target object,
using the given error description.void
Register a global error for the entire target object,
using the given error description.void
Register a field error for the specified field of the current object
(respecting the current nested path, if any), using the given error
description.void
Register a field error for the specified field of the current object
(respecting the current nested path, if any), using the given error
description.void
Register a field error for the specified field of the current object
(respecting the current nested path, if any), using the given error
description.void
Allow context to be changed so that standard validators can validate
subtrees.
-
Field Details
-
NESTED_PATH_SEPARATOR
static final String NESTED_PATH_SEPARATOR
The separator between path elements in a nested path,
for example in «customer.name» or «customer.address.street».«.» = same as the
nested property separator
in the beans package.- See Also:
-
- Constant Field Values
-
-
Method Details
-
getObjectName
Return the name of the bound root object.
-
setNestedPath
void setNestedPath(String nestedPath)
Allow context to be changed so that standard validators can validate
subtrees. Reject calls prepend the given path to the field names.For example, an address validator could validate the subobject
«address» of a customer object.- Parameters:
nestedPath
— nested path within this object,
e.g. «address» (defaults to «»,null
is also acceptable).
Can end with a dot: both «address» and «address.» are valid.
-
getNestedPath
Return the current nested path of this
Errors
object.Returns a nested path with a dot, i.e. «address.», for easy
building of concatenated paths. Default is an empty String. -
pushNestedPath
void pushNestedPath(String subPath)
Push the given sub path onto the nested path stack.
A
popNestedPath()
call will reset the original
nested path before the corresponding
pushNestedPath(String)
call.Using the nested path stack allows to set temporary nested paths
for subobjects without having to worry about a temporary path holder.For example: current path «spouse.», pushNestedPath(«child») →
result path «spouse.child.»; popNestedPath() → «spouse.» again.- Parameters:
subPath
— the sub path to push onto the nested path stack- See Also:
-
popNestedPath()
-
popNestedPath
Pop the former nested path from the nested path stack.
- Throws:
IllegalStateException
— if there is no former nested path on the stack- See Also:
-
pushNestedPath(java.lang.String)
-
reject
void reject(String errorCode)
Register a global error for the entire target object,
using the given error description.- Parameters:
errorCode
— error code, interpretable as a message key
-
reject
void reject(String errorCode,
String defaultMessage)Register a global error for the entire target object,
using the given error description.- Parameters:
errorCode
— error code, interpretable as a message keydefaultMessage
— fallback default message
-
reject
Register a global error for the entire target object,
using the given error description.- Parameters:
errorCode
— error code, interpretable as a message keyerrorArgs
— error arguments, for argument binding via MessageFormat
(can benull
)defaultMessage
— fallback default message
-
rejectValue
Register a field error for the specified field of the current object
(respecting the current nested path, if any), using the given error
description.The field name may be
null
or empty String to indicate
the current object itself rather than a field of it. This may result
in a corresponding field error within the nested object graph or a
global error if the current object is the top object.- Parameters:
field
— the field name (may benull
or empty String)errorCode
— error code, interpretable as a message key- See Also:
-
getNestedPath()
-
rejectValue
Register a field error for the specified field of the current object
(respecting the current nested path, if any), using the given error
description.The field name may be
null
or empty String to indicate
the current object itself rather than a field of it. This may result
in a corresponding field error within the nested object graph or a
global error if the current object is the top object.- Parameters:
field
— the field name (may benull
or empty String)errorCode
— error code, interpretable as a message keydefaultMessage
— fallback default message- See Also:
-
getNestedPath()
-
rejectValue
Register a field error for the specified field of the current object
(respecting the current nested path, if any), using the given error
description.The field name may be
null
or empty String to indicate
the current object itself rather than a field of it. This may result
in a corresponding field error within the nested object graph or a
global error if the current object is the top object.- Parameters:
field
— the field name (may benull
or empty String)errorCode
— error code, interpretable as a message keyerrorArgs
— error arguments, for argument binding via MessageFormat
(can benull
)defaultMessage
— fallback default message- See Also:
-
getNestedPath()
-
addAllErrors
void addAllErrors(Errors errors)
Add all errors from the given
Errors
instance to this
Errors
instance.This is a convenience method to avoid repeated
reject(..)
calls for merging anErrors
instance into another
Errors
instance.Note that the passed-in
Errors
instance is supposed
to refer to the same target object, or at least contain compatible errors
that apply to the target object of thisErrors
instance.- Parameters:
errors
— theErrors
instance to merge in
-
hasErrors
boolean hasErrors()
Return if there were any errors.
-
getErrorCount
int getErrorCount()
Return the total number of errors.
-
getAllErrors
Get all errors, both global and field ones.
- Returns:
- a list of
ObjectError
instances
-
hasGlobalErrors
boolean hasGlobalErrors()
Are there any global errors?
- Returns:
true
if there are any global errors- See Also:
-
hasFieldErrors()
-
getGlobalErrorCount
int getGlobalErrorCount()
Return the number of global errors.
- Returns:
- the number of global errors
- See Also:
-
getFieldErrorCount()
-
getGlobalErrors
Get all global errors.
- Returns:
- a list of
ObjectError
instances
-
getGlobalError
Get the first global error, if any.
- Returns:
- the global error, or
null
-
hasFieldErrors
boolean hasFieldErrors()
Are there any field errors?
- Returns:
true
if there are any errors associated with a field- See Also:
-
hasGlobalErrors()
-
getFieldErrorCount
int getFieldErrorCount()
Return the number of errors associated with a field.
- Returns:
- the number of errors associated with a field
- See Also:
-
getGlobalErrorCount()
-
getFieldErrors
Get all errors associated with a field.
- Returns:
- a List of
FieldError
instances
-
getFieldError
Get the first error associated with a field, if any.
- Returns:
- the field-specific error, or
null
-
hasFieldErrors
boolean hasFieldErrors(String field)
Are there any errors associated with the given field?
- Parameters:
field
— the field name- Returns:
true
if there were any errors associated with the given field
-
getFieldErrorCount
int getFieldErrorCount(String field)
Return the number of errors associated with the given field.
- Parameters:
field
— the field name- Returns:
- the number of errors associated with the given field
-
getFieldErrors
Get all errors associated with the given field.
Implementations should support not only full field names like
«name» but also pattern matches like «na*» or «address.*».- Parameters:
field
— the field name- Returns:
- a List of
FieldError
instances
-
getFieldError
Get the first error associated with the given field, if any.
- Parameters:
field
— the field name- Returns:
- the field-specific error, or
null
-
getFieldValue
Return the current value of the given field, either the current
bean property value or a rejected update from the last binding.Allows for convenient access to user-specified field values,
even if there were type mismatches.- Parameters:
field
— the field name- Returns:
- the current value of the given field
-
getFieldType
Return the type of a given field.
Implementations should be able to determine the type even
when the field value isnull
, for example from some
associated descriptor.- Parameters:
field
— the field name- Returns:
- the type of the field, or
null
if not determinable
-
get_fields is suddenly returning a null value after today’s update. No other changes were made to the site. I took over development from someone else, so this isn’t my code. Was this function depreciated or changed? After switching to get_post_fields, the expected values are returned.
Can you please check the code is get_field and not get_fields (note the s)
Same issue here. After updating to version 5.11, get_field returns null on my taxonomies:
get_field('my_field', $taxonomy . '_' . $term_id)
What happens if you toggle the return settings and resave, does that fix it?
So if it’s set to return an array, switch to ID, save, switch back and resave?
My bad, no “s”, should have copied paste that. Here was the code that returned null on a key being a taxonomy image slug:
$img = get_field($key);
Switched to this as a quick fix
$img = get_post_field( $key );
I’m seeing the same thing after the update.
All get_field/the_field functions return null.
I’m seeing this on multiple sites.
The fields aren’t added by the user or registered in ACF, they are imported into the post from the contents of an API.
Has there been a change where get_field will only return info if the field is registered by acf or data explicitly entered by the user?
Hey folks,
This is likely happening as a result of a recent security update to the logic behind get_field() and similar functions.
The problem was that before ACF 5.11, get_field() would return a value even if there wasn’t a matching field. This meant that it could be used to grab non-ACF data, including arbitrary options or user meta.
However, get_field() should still work in ACF 5.11, as long as the field that you want the value for exists.
We have seen some cases in support where fields/field groups registered via PHP (via acf_add_local_field_group(), acf_add_local_field(), and the like) are registered on a late action hook, or on a hook specific to the admin or front end, etc.
In those cases, though the field is registered, it may not available at the time that get_field() is called, so null is returned.
If you think that might be what’s happening to you, registering the fields on the “acf/init” action hook or just in your theme’s functions.php file should resolve the issue. Otherwise, feel free to reply here or shoot us an email so we can dig into it further.
In our project, those fields were not registered via PHP. They were created using the ACF WP Editor.
I’m going to update mine to use get_post_meta() instead as the fields in the API’s can change.
https://developer.wordpress.org/reference/functions/get_post_meta/
For anyone wanting to do the same, remember that if you just want a single value, you have to set the last parameter to true.
Otherwise, even if there’s only one field you will still get an array returned containing one item with the value you want.
@retroriff did yo try toggling the return options? Maybe also try changing the data in the field you are trying to get and save it as well.
The following code now returns nothing on the category.php template, used to work fine. I use this code on multiple sites:
<php while (have_posts()) : the_post(); ?>
<h3 class="text-center"><?php the_title(); ?></h3/>
<?php the_field('r4m_video_url'); ?>
<?php if ( get_field( 'r4m_video_description') ): ?>
<?php the_field('r4m_video_description'); ?>
<?php endif; ?>
<?php endwhile; ?>
‘r4m_video_url’ is a text field for iframe code.
Any ideas what is causing the error?
@aodesign have you tried following @jarvis suggestion above?
@robwent do you mean switch between the return array options, by editing the field group? Did it work for you? Text field doesn’t have different return formats, or perhaps I’m misunderstanding?
@aodesign No, you are correct.
I would try resaving the field in a post and see if that puts it back, or try specifying the post ID as a second parameter just in case.
If not, you can download the last version from the downloads page for a temporary fix if it’s a live site.
My issue was that the postmeta isn’t registered as an acf field but I was using get_field to get the information anyway.
@robwent thanks for your input. The field works in a post, but not in the category. I’ve rolled back the ACF plugin for the time being. At least my site is back up again.
This update made ACF completely unusable on my website. Rolling back the version this evening.
As mentioned by @mattshaw if you are using ACF to manage fields created by some other plugin or value then it is usually a bad idea to do so without creating an ACF field to replace the other field and then hide the other field so that it cannot be used.
As an example, on every site I built I replace the built in WP featured image field. I do this so that I can require a feature image because the front end design depends on the featured image existing. I create a field with the label “Featured Image” and the name “_thumbnail_id” which is identical to the meta key used by WP for holding the featured image ID. Then I hide the standard featured image input field. This makes it so that I can use get_field('_thumbnail_id')
and also makes the value available to all standard WP functions that deals with the featured image.
I can understand how this can lead to problems for some but the developers of ACF are enforcing what would be considered best practices. get_field()
should not be used for getting meta values for fields created outside of ACF. get_{$object_type}_meta()
should have been using all along in these cases.
Another reason of not using get_field() for non ACF fields is simply readability and needing to figure out code in the future. If I see get_field('field name')
I and other developers will automatically think that this is an ACF field and I will start looking for this field in the field groups if I need to change something. I will be completely confused when I don’t find that field. It will make the code much harder for others to work on or even myself a year after I’ve written it.
Another thing to consider is that ACF fields should always be defined no matter how/when they will be used and other methods of showing and hiding those fields should be used rather than conditionally creating the fields. This is one of the uses for the acf/prepare_field filter which can be used to conditionally include a field when showing a field group.
@hube2 It may be best practice, but this is also a breaking change that came without warning.
I’m not really clear on how this improves security, but it would have been a good idea to give a heads up considering how many sites are set to auto-update.
Here is a possible fix for those using get_field() for non ACF fields. This is untested. Most of it is pulled out of the function acf_get_metadata().
add_filter('acf/load_value', 'get_meta_or_option_for_acf_get_field', 20, 3);
function get_post_meta_for_acf_get_field($value, $post_id, $field) {
if ($value !== NULL) {
return $value;
}
$decoded = acf_decode_post_id( $post_id );
$id = $decoded['id'];
$type = $decoded['type'];
if (!$id) {
return $value;
}
$name = $field['name'];
if ($type === 'option') {
return get_option("{$id}_{$name}", NULL);
} else {
$meta = get_metadata($type, $id, $name, false);
return isset($meta[0]) ? $meta[0] : NULL;
}
}
@t0su @retroriff @aodesign @williamsp
If you’re still having issues, could you please shoot an email to [email protected] so we can troubleshoot further? We’re happy to help out, although it might be a bit tough on the forum.
@robwent I definitely see where you’re coming from. I think part of the issue is that we underestimated the number of folks that are using get_field()
to get values for fields that don’t technically exist or to get fields unrelated to ACF.
The other part of the issue is that this was part of a security fix, which we would typically prefer to hold off on disclosing until folks have had a chance to update. That said, we definitely could have done better at documenting this once we did release the update. We’ve since updated the docs for get_field()
to include some notes about this change, and updated the release post to point to those notes.
Regarding how this change improves security – the problem with get_field()
being able to get arbitrary values is that it opens up vulnerabilities with things like AJAX requests or form submissions where get_field()
might be manipulated by the request data.
We are seeing this issue as well.
We run quite a few large multi-sites and after using switch_to_blog() the get_field(‘something’, ‘options’) returns null.
This is a big issue for us.
We found a temp fix, but this seems like a breaking change that needs to be rolled back.
Joe
@floodlightdesign I believe that the only reason you did not have issues with this is the past is that you were using basic fields or possibly other types of fields that store simple text values. That also goes for getting fields with ACF when not created in ACF. The problem on multisite is that the fields need to be defined on the site you are switching from. There have been many topics over the years about this on multisite and ways to work around it. How you’d do this would be dependent on what you’re doing. If you post a new topic with the details I might be able to help you correct.
I’m not part of the ACF dev team, but I do support what they have implemented. However, I do understand how this can be a problem for those that have depended on what was basically an unintended feature and that it could mean rebuilding sites. That’s why I posted the code that I posted above. While I don’t have this issue, if I did I’d want to be able to update ACF and get site back up and running as quickly as possible until I could fix correctly (or not).
I will add that the code I provided will only work for fields that store simple text values in the DB because when the ACF field is not defined then ACF will be unable to format more complex fields correctly. But again, I’m assuming that most of the issue has to do with text based field since this flaw in ACF would never have worked with complex data types in the first place.
Appreciate the response – we are well aware of a fix. The fix on hundreds of sites becomes less straightforward.
The main concern is this should not have happened in the first place.
ACF Pro has been a solid part of our enterprise website implementation strategy for many years. I understand that issues happen, but this seems to be the first of its kind that has effected us so extensively. Changes that break or eliminate previous functionality need to be well publicized and documented.
Luckily for me, I only had 1 site affected and only 4 places in the code that needed updating to accommodate the new security feature. Being in a full-time cybersecurity role, I have a lot of appreciation for this feature so that it doesn’t bleed over from unintended functionality. However, more disclosure/documentation about this update and how to mitigate would have been nice for developers so that our sites don’t suddenly go down after auto-updating (as it was for mine) and then scrambling to figure out what caused it.
I’ve never used this particular plugin in combination with custom developments, so seeing the format of the function name I originally assumed it was a built-in function to WordPress. Took me a little while to figure out no built-in function existed and this was part of ACF. Perhaps adding the plugin tag infront of the function, such as acf_get_field, could have helped ID this as a plugin issue sooner. I appreciate all the support this forum has provided, which will have me considering using this more often for future projects.
It’s totally NOT professional.
Does the new developer REALLY forgot that ACF supports loading local fields from a JSON file, i.e. the fields definition will NOT exist per se, but will exist in a json that ACF loads.
In my case all my :
get_field(‘blabla_option_definition_that_should_be_loaded_from_json’, ‘option’)
are now returning NULL instead of something, breaking all logic based on that.
In case the new developer didn’t know that too : ACF is also used to generate Options page that have sitewide effects: https://www.advancedcustomfields.com/resources/options-page/
As a result I have stored ACF field definitions in a JSON file that is totally ignored and thus all sitewide settings stored are ignored.
And this has been reported on the 10th but you let everyone discover your error, losing money for 3 days wondering what was going on…
What the hell are you waiting for AT LEAST inform every pro users that there is a glitch and that they should check everything is working fine???
Very very upset to see this plugin has been acquired and how it’s now being developed.
Viewing 25 posts — 1 through 25 (of 88 total)