merged branch bschussek/form-debugger (PR #9082)

This PR was merged into the master branch.

Discussion
----------

[Form] Implemented form debugger

Same as #9021 (kudos to @digitalkaoz!), with some added caramel.

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

**Note:** Please keep in mind that this is a first version only. Not all features (especially UX/UI-wise) that I'd like to see are in there, but it's good enough for a first merge.

Commits
-------

89509d9 [Form] Improved form debugger
f56c577 [HttpKernel] Extracted value exporting logic of DataCollector into a separate ValueExporter class
56d78ed [Form] Decoupled methods of ResolvedFormType so that they can be overridden individually by decorators
a994a5d [Form] Merged subsriber/collector, also collect valid forms
1972a91 [Form] Added form debug collector
This commit is contained in:
Fabien Potencier 2013-09-25 19:00:08 +02:00
commit 55de9d9746
28 changed files with 2748 additions and 224 deletions

View File

@ -218,6 +218,7 @@ class FrameworkExtension extends Extension
return;
}
$loader->load('form_debug.xml');
$loader->load('profiling.xml');
$loader->load('collectors.xml');

View File

@ -13,6 +13,8 @@
<parameter key="data_collector.time.class">Symfony\Component\HttpKernel\DataCollector\TimeDataCollector</parameter>
<parameter key="data_collector.memory.class">Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector</parameter>
<parameter key="data_collector.router.class">Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector</parameter>
<parameter key="data_collector.form.class">Symfony\Component\Form\Extension\DataCollector\FormDataCollector</parameter>
<parameter key="data_collector.form.extractor.class">Symfony\Component\Form\Extension\DataCollector\FormDataExtractor</parameter>
</parameters>
<services>
@ -53,5 +55,13 @@
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
<tag name="data_collector" template="@WebProfiler/Collector/router.html.twig" id="router" priority="255" />
</service>
<service id="data_collector.form.extractor" class="%data_collector.form.extractor.class%" />
<service id="data_collector.form" class="%data_collector.form.class%">
<tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="255" />
<argument type="service" id="data_collector.form.extractor" />
</service>
</services>
</container>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="form.resolved_type_factory.data_collector_proxy.class">Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy</parameter>
<parameter key="form.type_extension.form.data_collector.class">Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension</parameter>
</parameters>
<services>
<service id="form.resolved_type_factory" class="%form.resolved_type_factory.data_collector_proxy.class%">
<argument type="service">
<service class="%form.resolved_type_factory.class%" />
</argument>
<argument type="service" id="data_collector.form" />
</service>
<!-- DataCollectorTypeExtension -->
<service id="form.type_extension.form.data_collector" class="%form.type_extension.form.data_collector.class%">
<tag name="form.type_extension" alias="form" />
<argument type="service" id="data_collector.form" />
</service>
</services>
</container>

View File

@ -0,0 +1,345 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% from _self import form_tree_entry, form_tree_details %}
{% block toolbar %}
{% if collector.data|length %}
{% set icon %}
<img width="20" height="28" alt="Forms" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDNkE1RUQ2RjBFMzcxMUUzOEIyQkVBRkQ5QzNENTIxMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDNkE1RUQ3MDBFMzcxMUUzOEIyQkVBRkQ5QzNENTIxMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkM2QTVFRDZEMEUzNzExRTM4QjJCRUFGRDlDM0Q1MjEzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkM2QTVFRDZFMEUzNzExRTM4QjJCRUFGRDlDM0Q1MjEzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+fkaAkgAAAQtJREFUeNpi/P//PwM1ARMDlQGLg4MDgsPCAqb//PnDwMzMDGJ+//v3L4oGqDgnSBxZPdwMLJa4ggioQRzoklBxVySh3SguBOICGOffv38TgNQuIny2C6oeRDEim8Fob28PjxVgBIEk/zMyMhIVXqAIZYQo/o81UlhZWcGYWIBNLQta+PACKS1iDYSGJy9OA4Fe+EBKUsKWhlmwpMtZZCS/NHzJ5iFFCRuN7wXEEtQ0sJDirIfGrwBiLiL0HSbWwLPoWQkH2I0tu+KKlOVk+BSngclALEnNMHSldqTMJtOcCFwGNlDbhZ1Y1LwhwpwTuAz0waI4gxIXPqC2lzuoYeAPalajjIO+ogcIMAAaCkonoOPIhQAAAABJRU5ErkJggg=="/>
<span class="sf-toolbar-status sf-toolbar-status-{% if collector.data.nb_errors %}red{% else %}green{% endif %}">{% if collector.data.nb_errors %}{{ collector.data.nb_errors }}{% else %}{{ collector.data.forms|length }}{% endif %}</span>
{% endset %}
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label">
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAbCAYAAAB836/YAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh3ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAIpJREFUeNrslFEKgDAMQ1PpzrX7n6mF+KMfTjvnnKBgYD+heaQUJjlnRFJVunvpwd0lykyoqIRFXhWoqgSwvkgEwGV2my+CctYgaMumlXu0A6aUmsNHs1oaZiatQDM7BXL4ynelB550cFgDcmRD6eTwsaP0NmQrkP+Vv3vlv+G1hrwDfN8HOxw4DwC6ITLy7UIfRQAAAABJRU5ErkJggg==" alt=""/></span>
<strong>Forms</strong>
{% if collector.data.forms|length %}
<span class="count"><span>{{ collector.data.forms|length }}</span></span>
{% endif %}
</span>
{% endblock %}
{% block panel %}
<style type="text/css">
.window {
/*background: #F6F6F6;*/
margin: -30px -40px -40px;
}
.tree {
width: 230px;
padding: 10px;
font-size: 12px;
float: left;
}
#content .tree h2 {
font-size: 13px;
padding: 5px 7px;
margin: 0;
}
.tree li {
margin: 0;
padding: 0;
width: 100%;
}
.tree a {
text-decoration: none;
display: block;
padding: 5px 7px;
border-radius: 6px;
color: #313131;
}
.tree ul ul a {
padding-left: 22px;
}
.tree ul ul ul a {
padding-left: 37px;
}
.tree ul ul ul ul a {
padding-left: 52px;
}
.tree ul ul ul ul ul a {
padding-left: 67px;
}
.tree a:hover {
background: #dfdfdf;
}
.tree a.active, a.active:hover {
background: #dfdfdf;
font-weight: bold;
color: #313131;
}
.tree-details {
border-left: 1px solid #dfdfdf;
background: white;
margin-left: 250px;
padding: 30px 40px 40px;
}
.form-type {
color: #999999;
}
.hidden {
display: none;
}
</style>
{% if collector.data.forms|length %}
<div class="window">
<div class="tree">
<h2>Forms</h2>
<ul>
{% for formName, formData in collector.data.forms %}
{{ form_tree_entry(formName, formData) }}
{% endfor %}
</ul>
</div>
{% for formName, formData in collector.data.forms %}
{{ form_tree_details(formName, formData) }}
{% endfor %}
</div>
{% else %}
<p><em>No forms were submitted for this request.</em></p>
{% endif %}
<script>
function TabView() {
var _activeLink = null,
_activeView = null;
this.init = function () {
var links = document.querySelectorAll('.tree a'),
views = document.querySelectorAll('.tree-details'),
i,
l;
for (i = 0, l = links.length; i < l; ++i) {
(function () {
var link = links[i];
link.addEventListener('click', function (e) {
var href = link.getAttribute('href'),
targetId = href.substr(href.indexOf('#') + 1),
view = document.getElementById(targetId);
if (view) {
if (null !== _activeLink) {
Sfjs.removeClass(_activeLink, 'active');
}
if (null !== _activeView) {
Sfjs.addClass(_activeView, 'hidden');
}
Sfjs.addClass(link, 'active');
Sfjs.removeClass(view, 'hidden');
_activeLink = link;
_activeView = view;
}
e.preventDefault();
return false;
})
}());
}
for (i = 0, l = views.length; i < l; ++i) {
Sfjs.addClass(views[i], 'hidden');
}
if (links.length > 0) {
Sfjs.addClass(links[0], 'active');
_activeLink = links[0];
if (views.length > 0) {
Sfjs.removeClass(views[0], 'hidden');
_activeView = views[0];
}
}
}
}
var tabView = new TabView();
tabView.init();
</script>
{% endblock %}
{% macro form_tree_entry(name, data) %}
<li>
<a href="#details_{{ data.view_vars.id|default("") }}">{{ name }}</a>
{% if data.children|length > 1 %}
<ul>
{% for childName, childData in data.children %}
{{ _self.form_tree_entry(childName, childData) }}
{% endfor %}
</ul>
{% endif %}
</li>
{% endmacro %}
{% macro form_tree_details(name, data) %}
<div class="tree-details" id="details_{{ data.view_vars.id|default("") }}">
<h2>
{{ name }}
{% if data.type_class is defined %}
<span class="form-type">[<abbr title="{{ data.type_class }}">{{ data.type }}</abbr>]</span>
{% endif %}
</h2>
{% if data.errors is defined and data.errors|length > 0 %}
<h3>Errors</h3>
<table>
<tr>
<th width="50%">Message</th>
<th>Cause</th>
</tr>
{% for error in data.errors %}
<tr>
<td>{{ error.message }}</td>
<td><em>Unknown.</em></td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if data.default_data is defined %}
<h3>Default Data</h3>
<table>
<tr>
<th width="180">Model Format</th>
<td>
{% if data.default_data.model is defined %}
<pre>{{ data.default_data.model }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
<tr>
<th>Normalized Format</th>
<td><pre>{{ data.default_data.norm }}</pre></td>
</tr>
<tr>
<th>View Format</th>
<td>
{% if data.default_data.view is defined %}
<pre>{{ data.default_data.view }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
</table>
{% endif %}
{% if data.submitted_data is defined %}
<h3>Submitted Data</h3>
{% if data.submitted_data.norm is defined %}
<table>
<tr>
<th width="180">View Format</th>
<td>
{% if data.submitted_data.view is defined %}
<pre>{{ data.submitted_data.view }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
<tr>
<th>Normalized Format</th>
<td><pre>{{ data.submitted_data.norm }}</pre></td>
</tr>
<tr>
<th>Model Format</th>
<td>
{% if data.submitted_data.model is defined %}
<pre>{{ data.submitted_data.model }}</pre>
{% else %}
<em>same as normalized format</em>
{% endif %}
</td>
</tr>
</table>
{% else %}
<p><em>This form was not submitted.</em></p>
{% endif %}
{% endif %}
{% if data.passed_options is defined %}
<h3>Passed Options</h3>
{% if data.passed_options|length %}
<table>
<tr>
<th width="180">Option</th>
<th>Passed Value</th>
<th>Resolved Value</th>
</tr>
{% for option, value in data.passed_options %}
<tr>
<th>{{ option }}</th>
<td><pre>{{ value }}</pre></td>
<td>
{% if data.resolved_options[option] is sameas(value) %}
<em>same as passed value</em>
{% else %}
<pre>{{ data.resolved_options[option] }}</pre>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p><em>No options where passed when constructing this form.</em></p>
{% endif %}
{% endif %}
{% if data.resolved_options is defined %}
<h3>Resolved Options</h3>
<table>
<tr>
<th width="180">Option</th>
<th>Value</th>
</tr>
{% for option, value in data.resolved_options %}
<tr>
<th>{{ option }}</th>
<td><pre>{{ value }}</pre></td>
</tr>
{% endfor %}
</table>
{% endif %}
<h3>View Variables</h3>
<table>
<tr>
<th width="180">Variable</th>
<th>Value</th>
</tr>
{% for variable, value in data.view_vars %}
<tr>
<th>{{ variable }}</th>
<td><pre>{{ value }}</pre></td>
</tr>
{% endfor %}
</table>
</div>
{% for childName, childData in data.children %}
{{ _self.form_tree_details(childName, childData) }}
{% endfor %}
{% endmacro %}

View File

@ -63,6 +63,9 @@ table th, table td {
font-size: 12px;
padding: 8px 10px;
}
table td em {
color: #aaa;
}
fieldset {
border: none;
}
@ -70,6 +73,9 @@ abbr {
border-bottom: 1px dotted #000;
cursor: help;
}
pre, code {
font-size: 0.9em;
}
.clear {
clear: both;
height: 0;

View File

@ -412,7 +412,16 @@ class Button implements \IteratorAggregate, FormInterface
$parent = $this->parent->createView();
}
return $this->config->getType()->createView($this, $parent);
$type = $this->config->getType();
$options = $this->config->getOptions();
$view = $type->createView($this, $parent);
$type->buildView($view, $this, $options);
$type->finishView($view, $this, $options);
return $view;
}
/**

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractExtension;
/**
* Extension for collecting data of the forms on a page.
*
* @since 2.4
* @author Robert Schönthal <robert.schoenthal@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DataCollectorExtension extends AbstractExtension
{
/**
* @var EventSubscriberInterface
*/
private $dataCollector;
public function __construct(FormDataCollectorInterface $dataCollector)
{
$this->dataCollector = $dataCollector;
}
/**
* {@inheritDoc}
*/
protected function loadTypeExtensions()
{
return array(
new Type\DataCollectorTypeExtension($this->dataCollector)
);
}
}

View File

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* Listener that invokes a data collector for the {@link FormEvents::POST_SET_DATA}
* and {@link FormEvents::POST_SUBMIT} events.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DataCollectorListener implements EventSubscriberInterface
{
/**
* @var FormDataCollectorInterface
*/
private $dataCollector;
public function __construct(FormDataCollectorInterface $dataCollector)
{
$this->dataCollector = $dataCollector;
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
// High priority in order to be called as soon as possible
FormEvents::POST_SET_DATA => array('postSetData', 255),
// Low priority in order to be called as late as possible
FormEvents::POST_SUBMIT => array('postSubmit', -255),
);
}
/**
* Listener for the {@link FormEvents::POST_SET_DATA} event.
*
* @param FormEvent $event The event object
*/
public function postSetData(FormEvent $event)
{
if ($event->getForm()->isRoot()) {
// Collect basic information about each form
$this->dataCollector->collectConfiguration($event->getForm());
// Collect the default data
$this->dataCollector->collectDefaultData($event->getForm());
}
}
/**
* Listener for the {@link FormEvents::POST_SUBMIT} event.
*
* @param FormEvent $event The event object
*/
public function postSubmit(FormEvent $event)
{
if ($event->getForm()->isRoot()) {
// Collect the submitted data of each form
$this->dataCollector->collectSubmittedData($event->getForm());
// Assemble a form tree
// This is done again in collectViewVariables(), but that method
// is not guaranteed to be called (i.e. when no view is created)
$this->dataCollector->buildPreliminaryFormTree($event->getForm());
}
}
}

View File

@ -0,0 +1,274 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Data collector for {@link \Symfony\Component\Form\FormInterface} instances.
*
* @since 2.4
* @author Robert Schönthal <robert.schoenthal@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormDataCollector extends DataCollector implements FormDataCollectorInterface
{
/**
* @var FormDataExtractor
*/
private $dataExtractor;
/**
* Stores the collected data per {@link FormInterface} instance.
*
* Uses the hashes of the forms as keys. This is preferrable over using
* {@link \SplObjectStorage}, because in this way no references are kept
* to the {@link FormInterface} instances.
*
* @var array
*/
private $dataByForm;
/**
* Stores the collected data per {@link FormView} instance.
*
* Uses the hashes of the views as keys. This is preferrable over using
* {@link \SplObjectStorage}, because in this way no references are kept
* to the {@link FormView} instances.
*
* @var array
*/
private $dataByView;
/**
* Connects {@link FormView} with {@link FormInterface} instances.
*
* Uses the hashes of the views as keys and the hashes of the forms as
* values. This is preferrable over storing the objects directly, because
* this way they can safely be discarded by the GC.
*
* @var array
*/
private $formsByView;
public function __construct(FormDataExtractorInterface $dataExtractor)
{
$this->dataExtractor = $dataExtractor;
$this->data = array(
'forms' => array(),
'nb_errors' => 0,
);
}
/**
* Does nothing. The data is collected during the form event listeners.
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
}
/**
* {@inheritdoc}
*/
public function associateFormWithView(FormInterface $form, FormView $view)
{
$this->formsByView[spl_object_hash($view)] = spl_object_hash($form);
}
/**
* {@inheritdoc}
*/
public function collectConfiguration(FormInterface $form)
{
$hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = array();
}
$this->dataByForm[$hash] = array_replace(
$this->dataByForm[$hash],
$this->dataExtractor->extractConfiguration($form)
);
foreach ($form as $child) {
$this->collectConfiguration($child);
}
}
/**
* {@inheritdoc}
*/
public function collectDefaultData(FormInterface $form)
{
$hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = array();
}
$this->dataByForm[$hash] = array_replace(
$this->dataByForm[$hash],
$this->dataExtractor->extractDefaultData($form)
);
foreach ($form as $child) {
$this->collectDefaultData($child);
}
}
/**
* {@inheritdoc}
*/
public function collectSubmittedData(FormInterface $form)
{
$hash = spl_object_hash($form);
if (!isset($this->dataByForm[$hash])) {
$this->dataByForm[$hash] = array();
}
$this->dataByForm[$hash] = array_replace(
$this->dataByForm[$hash],
$this->dataExtractor->extractSubmittedData($form)
);
// Count errors
if (isset($this->dataByForm[$hash]['errors'])) {
$this->data['nb_errors'] += count($this->dataByForm[$hash]['errors']);
}
foreach ($form as $child) {
$this->collectSubmittedData($child);
}
}
/**
* {@inheritdoc}
*/
public function collectViewVariables(FormView $view)
{
$hash = spl_object_hash($view);
if (!isset($this->dataByView[$hash])) {
$this->dataByView[$hash] = array();
}
$this->dataByView[$hash] = array_replace(
$this->dataByView[$hash],
$this->dataExtractor->extractViewVariables($view)
);
foreach ($view->children as $child) {
$this->collectViewVariables($child);
}
}
/**
* {@inheritdoc}
*/
public function buildPreliminaryFormTree(FormInterface $form)
{
$this->data['forms'][$form->getName()] = array();
$this->recursiveBuildPreliminaryFormTree($form, $this->data['forms'][$form->getName()]);
}
/**
* {@inheritdoc}
*/
public function buildFinalFormTree(FormInterface $form, FormView $view)
{
$this->data['forms'][$form->getName()] = array();
$this->recursiveBuildFinalFormTree($form, $view, $this->data['forms'][$form->getName()]);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'form';
}
/**
* {@inheritdoc}
*/
public function getData()
{
return $this->data;
}
private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null)
{
$hash = spl_object_hash($form);
$output = isset($this->dataByForm[$hash])
? $this->dataByForm[$hash]
: array();
$output['children'] = array();
foreach ($form as $name => $child) {
$output['children'][$name] = array();
$this->recursiveBuildPreliminaryFormTree($child, $output['children'][$name]);
}
}
private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null)
{
$viewHash = spl_object_hash($view);
$formHash = null;
if (null !== $form) {
$formHash = spl_object_hash($form);
} elseif (isset($this->formsByView[$viewHash])) {
// The FormInterface instance of the CSRF token is never contained in
// the FormInterface tree of the form, so we need to get the
// corresponding FormInterface instance for its view in a different way
$formHash = $this->formsByView[$viewHash];
}
$output = isset($this->dataByView[$viewHash])
? $this->dataByView[$viewHash]
: array();
if (null !== $formHash) {
$output = array_replace(
$output,
isset($this->dataByForm[$formHash])
? $this->dataByForm[$formHash]
: array()
);
}
$output['children'] = array();
foreach ($view->children as $name => $childView) {
// The CSRF token, for example, is never added to the form tree.
// It is only present in the view.
$childForm = null !== $form && $form->has($name)
? $form->get($name)
: null;
$output['children'][$name] = array();
$this->recursiveBuildFinalFormTree($childForm, $childView, $output['children'][$name]);
}
}
}

View File

@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
/**
* Collects and structures information about forms.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormDataCollectorInterface extends DataCollectorInterface
{
/**
* Stores configuration data of the given form and its children.
*
* @param FormInterface $form A root form
*/
public function collectConfiguration(FormInterface $form);
/**
* Stores the default data of the given form and its children.
*
* @param FormInterface $form A root form
*/
public function collectDefaultData(FormInterface $form);
/**
* Stores the submitted data of the given form and its children.
*
* @param FormInterface $form A root form
*/
public function collectSubmittedData(FormInterface $form);
/**
* Stores the view variables of the given form view and its children.
*
* @param FormView $view A root form view
*/
public function collectViewVariables(FormView $view);
/**
* Specifies that the given objects represent the same conceptual form.
*
* @param FormInterface $form A form object
* @param FormView $view A view object
*/
public function associateFormWithView(FormInterface $form, FormView $view);
/**
* Assembles the data collected about the given form and its children as
* a tree-like data structure.
*
* The result can be queried using {@link getData()}.
*
* @param FormInterface $form A root form
*/
public function buildPreliminaryFormTree(FormInterface $form);
/**
* Assembles the data collected about the given form and its children as
* a tree-like data structure.
*
* The result can be queried using {@link getData()}.
*
* Contrary to {@link buildPreliminaryFormTree()}, a {@link FormView}
* object has to be passed. The tree structure of this view object will be
* used for structuring the resulting data. That means, if a child is
* present in the view, but not in the form, it will be present in the final
* data array anyway.
*
* When {@link FormView} instances are present in the view tree, for which
* no corresponding {@link FormInterface} objects can be found in the form
* tree, only the view data will be included in the result. If a
* corresponding {@link FormInterface} exists otherwise, call
* {@link associateFormWithView()} before calling this method.
*
* @param FormInterface $form A root form
* @param FormView $view A root view
*/
public function buildFinalFormTree(FormInterface $form, FormView $view);
/**
* Returns all collected data.
*
* @return array
*/
public function getData();
}

View File

@ -0,0 +1,135 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
/**
* Default implementation of {@link FormDataExtractorInterface}.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormDataExtractor implements FormDataExtractorInterface
{
/**
* @var ValueExporter
*/
private $valueExporter;
/**
* Constructs a new data extractor.
*/
public function __construct(ValueExporter $valueExporter = null)
{
$this->valueExporter = $valueExporter ?: new ValueExporter();
}
/**
* {@inheritdoc}
*/
public function extractConfiguration(FormInterface $form)
{
$data = array(
'type' => $form->getConfig()->getType()->getName(),
'type_class' => get_class($form->getConfig()->getType()->getInnerType()),
'synchronized' => $this->valueExporter->exportValue($form->isSynchronized()),
'passed_options' => array(),
'resolved_options' => array(),
);
foreach ($form->getConfig()->getAttribute('data_collector/passed_options', array()) as $option => $value) {
$data['passed_options'][$option] = $this->valueExporter->exportValue($value);
}
foreach ($form->getConfig()->getOptions() as $option => $value) {
$data['resolved_options'][$option] = $this->valueExporter->exportValue($value);
}
ksort($data['passed_options']);
ksort($data['resolved_options']);
return $data;
}
/**
* {@inheritdoc}
*/
public function extractDefaultData(FormInterface $form)
{
$data = array(
'default_data' => array(
'norm' => $this->valueExporter->exportValue($form->getNormData()),
),
'submitted_data' => array(),
);
if ($form->getData() !== $form->getNormData()) {
$data['default_data']['model'] = $this->valueExporter->exportValue($form->getData());
}
if ($form->getViewData() !== $form->getNormData()) {
$data['default_data']['view'] = $this->valueExporter->exportValue($form->getViewData());
}
return $data;
}
/**
* {@inheritdoc}
*/
public function extractSubmittedData(FormInterface $form)
{
$data = array(
'submitted_data' => array(
'norm' => $this->valueExporter->exportValue($form->getNormData()),
),
'errors' => array(),
);
if ($form->getViewData() !== $form->getNormData()) {
$data['submitted_data']['view'] = $this->valueExporter->exportValue($form->getViewData());
}
if ($form->getData() !== $form->getNormData()) {
$data['submitted_data']['model'] = $this->valueExporter->exportValue($form->getData());
}
foreach ($form->getErrors() as $error) {
$data['errors'][] = array(
'message' => $error->getMessage(),
);
}
$data['synchronized'] = $this->valueExporter->exportValue($form->isSynchronized());
return $data;
}
/**
* {@inheritdoc}
*/
public function extractViewVariables(FormView $view)
{
$data = array();
foreach ($view->vars as $varName => $value) {
$data['view_vars'][$varName] = $this->valueExporter->exportValue($value);
}
ksort($data['view_vars']);
return $data;
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
/**
* Extracts arrays of information out of forms.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormDataExtractorInterface
{
/**
* Extracts the configuration data of a form.
*
* @param FormInterface $form The form
*
* @return array Information about the form's configuration
*/
public function extractConfiguration(FormInterface $form);
/**
* Extracts the default data of a form.
*
* @param FormInterface $form The form
*
* @return array Information about the form's default data
*/
public function extractDefaultData(FormInterface $form);
/**
* Extracts the submitted data of a form.
*
* @param FormInterface $form The form
*
* @return array Information about the form's submitted data
*/
public function extractSubmittedData(FormInterface $form);
/**
* Extracts the view variables of a form.
*
* @param FormView $view The form view
*
* @return array Information about the view's variables
*/
public function extractViewVariables(FormView $view);
}

View File

@ -0,0 +1,148 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector\Proxy;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\ResolvedFormTypeInterface;
/**
* Proxy that invokes a data collector when creating a form and its view.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface
{
/**
* @var ResolvedFormTypeInterface
*/
private $proxiedType;
/**
* @var FormDataCollectorInterface
*/
private $dataCollector;
public function __construct(ResolvedFormTypeInterface $proxiedType, FormDataCollectorInterface $dataCollector)
{
$this->proxiedType = $proxiedType;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->proxiedType->getName();
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return $this->proxiedType->getParent();
}
/**
* {@inheritdoc}
*/
public function getInnerType()
{
return $this->proxiedType->getInnerType();
}
/**
* {@inheritdoc}
*/
public function getTypeExtensions()
{
return $this->proxiedType->getTypeExtensions();
}
/**
* {@inheritdoc}
*/
public function createBuilder(FormFactoryInterface $factory, $name, array $options = array())
{
$builder = $this->proxiedType->createBuilder($factory, $name, $options);
$builder->setAttribute('data_collector/passed_options', $options);
$builder->setType($this);
return $builder;
}
/**
* {@inheritdoc}
*/
public function createView(FormInterface $form, FormView $parent = null)
{
return $this->proxiedType->createView($form, $parent);
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->proxiedType->buildForm($builder, $options);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$this->proxiedType->buildView($view, $form, $options);
}
/**
* {@inheritdoc}
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
$this->proxiedType->finishView($view, $form, $options);
// Remember which view belongs to which form instance, so that we can
// get the collected data for a view when its form instance is not
// available (e.g. CSRF token)
$this->dataCollector->associateFormWithView($form, $view);
// Since the CSRF token is only present in the FormView tree, we also
// need to check the FormView tree instead of calling isRoot() on the
// FormInterface tree
if (null === $view->parent) {
$this->dataCollector->collectViewVariables($view);
// Re-assemble data, in case FormView instances were added, for
// which no FormInterface instances were present (e.g. CSRF token).
// Since finishView() is called after finishing the views of all
// children, we can safely assume that information has been
// collected about the complete form tree.
$this->dataCollector->buildFinalFormTree($form, $view);
}
}
/**
* {@inheritdoc}
*/
public function getOptionsResolver()
{
return $this->proxiedType->getOptionsResolver();
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector\Proxy;
use Symfony\Component\Form\Exception;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\Form\ResolvedFormTypeFactoryInterface;
use Symfony\Component\Form\ResolvedFormTypeInterface;
/**
* Proxy that wraps resolved types into {@link ResolvedTypeDataCollectorProxy}
* instances.
*
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ResolvedTypeFactoryDataCollectorProxy implements ResolvedFormTypeFactoryInterface
{
/**
* @var ResolvedFormTypeFactoryInterface
*/
private $proxiedFactory;
/**
* @var FormDataCollectorInterface
*/
private $dataCollector;
public function __construct(ResolvedFormTypeFactoryInterface $proxiedFactory, FormDataCollectorInterface $dataCollector)
{
$this->proxiedFactory = $proxiedFactory;
$this->dataCollector = $dataCollector;
}
/**
* {@inheritdoc}
*/
public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null)
{
return new ResolvedTypeDataCollectorProxy(
$this->proxiedFactory->createResolvedType($type, $typeExtensions, $parent),
$this->dataCollector
);
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\DataCollector\Type;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Type extension for collecting data of a form with this type.
*
* @since 2.4
* @author Robert Schönthal <robert.schoenthal@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DataCollectorTypeExtension extends AbstractTypeExtension
{
/**
* @var \Symfony\Component\EventDispatcher\EventSubscriberInterface
*/
private $listener;
public function __construct(FormDataCollectorInterface $dataCollector)
{
$this->listener = new DataCollectorListener($dataCollector);
}
/**
* {@inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber($this->listener);
}
/**
* {@inheritDoc}
*/
public function getExtendedType()
{
return 'form';
}
}

View File

@ -969,7 +969,23 @@ class Form implements \IteratorAggregate, FormInterface
$parent = $this->parent->createView();
}
return $this->config->getType()->createView($this, $parent);
$type = $this->config->getType();
$options = $this->config->getOptions();
// The methods createView(), buildView() and finishView() are called
// explicitly here in order to be able to override either of them
// in a custom resolved form type.
$view = $type->createView($this, $parent);
$type->buildView($view, $this, $options);
foreach ($this->children as $name => $child) {
$view->children[$name] = $child->createView($view);
}
$type->finishView($view, $this, $options);
return $view;
}
/**

View File

@ -84,7 +84,13 @@ class FormFactory implements FormFactoryInterface
throw new UnexpectedTypeException($type, 'string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface');
}
return $type->createBuilder($this, $name, $options);
$builder = $type->createBuilder($this, $name, $options);
// Explicitly call buildForm() in order to be able to override either
// createBuilder() or buildForm() in the resolved form type
$type->buildForm($builder, $builder->getOptions());
return $builder;
}
/**

View File

@ -114,8 +114,6 @@ class ResolvedFormType implements ResolvedFormTypeInterface
$builder = $this->newBuilder($name, $dataClass, $factory, $options);
$builder->setType($this);
$this->buildForm($builder, $options);
return $builder;
}
@ -124,28 +122,12 @@ class ResolvedFormType implements ResolvedFormTypeInterface
*/
public function createView(FormInterface $form, FormView $parent = null)
{
$options = $form->getConfig()->getOptions();
$view = $this->newView($parent);
$this->buildView($view, $form, $options);
foreach ($form as $name => $child) {
/* @var FormInterface $child */
$view->children[$name] = $child->createView($view);
}
$this->finishView($view, $form, $options);
return $view;
return $this->newView($parent);
}
/**
* Configures a form builder for the type hierarchy.
*
* This method is protected in order to allow implementing classes
* to change or call it in re-implementations of {@link createBuilder()}.
*
* @param FormBuilderInterface $builder The builder to configure.
* @param array $options The options used for the configuration.
*/
@ -166,10 +148,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* Configures a form view for the type hierarchy.
*
* This method is protected in order to allow implementing classes
* to change or call it in re-implementations of {@link createView()}.
*
* It is called before the children of the view are built.
* This method is called before the children of the view are built.
*
* @param FormView $view The form view to configure.
* @param FormInterface $form The form corresponding to the view.
@ -192,10 +171,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* Finishes a form view for the type hierarchy.
*
* This method is protected in order to allow implementing classes
* to change or call it in re-implementations of {@link createView()}.
*
* It is called after the children of the view have been built.
* This method is called after the children of the view have been built.
*
* @param FormView $view The form view to configure.
* @param FormInterface $form The form corresponding to the view.
@ -218,9 +194,6 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* Returns the configured options resolver used for this type.
*
* This method is protected in order to allow implementing classes
* to change or call it in re-implementations of {@link createBuilder()}.
*
* @return \Symfony\Component\OptionsResolver\OptionsResolverInterface The options resolver.
*/
public function getOptionsResolver()

View File

@ -60,9 +60,9 @@ abstract class AbstractFormTest extends \PHPUnit_Framework_TestCase
*
* @return FormBuilder
*/
protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null, $dataClass = null)
protected function getBuilder($name = 'name', EventDispatcherInterface $dispatcher = null, $dataClass = null, array $options = array())
{
return new FormBuilder($name, $dataClass, $dispatcher ?: $this->dispatcher, $this->factory);
return new FormBuilder($name, $dataClass, $dispatcher ?: $this->dispatcher, $this->factory, $options);
}
/**

View File

@ -15,6 +15,7 @@ use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
@ -806,6 +807,65 @@ class CompoundFormTest extends AbstractFormTest
$this->assertEquals("name:\n ERROR: Error!\nfoo:\n No errors\n", $parent->getErrorsAsString());
}
// Basic cases are covered in SimpleFormTest
public function testCreateViewWithChildren()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$options = array('a' => 'Foo', 'b' => 'Bar');
$field1 = $this->getMockForm('foo');
$field2 = $this->getMockForm('bar');
$view = new FormView();
$field1View = new FormView();
$field2View = new FormView();
$this->form = $this->getBuilder('form', null, null, $options)
->setCompound(true)
->setDataMapper($this->getDataMapper())
->setType($type)
->getForm();
$this->form->add($field1);
$this->form->add($field2);
$test = $this;
$assertChildViewsEqual = function (array $childViews) use ($test) {
return function (FormView $view) use ($test, $childViews) {
/* @var \PHPUnit_Framework_TestCase $test */
$test->assertSame($childViews, $view->children);
};
};
// First create the view
$type->expects($this->once())
->method('createView')
->will($this->returnValue($view));
// Then build it for the form itself
$type->expects($this->once())
->method('buildView')
->with($view, $this->form, $options)
->will($this->returnCallback($assertChildViewsEqual(array())));
// Then add the first child form
$field1->expects($this->once())
->method('createView')
->will($this->returnValue($field1View));
// Then the second child form
$field2->expects($this->once())
->method('createView')
->will($this->returnValue($field2View));
// Again build the view for the form itself. This time the child views
// exist.
$type->expects($this->once())
->method('finishView')
->with($view, $this->form, $options)
->will($this->returnCallback($assertChildViewsEqual(array('foo' => $field1View, 'bar' => $field2View))));
$this->assertSame($view, $this->form->createView());
}
protected function createForm()
{
return $this->getBuilder()

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\DataCollector;
use Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension;
/**
* @covers Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension
*/
class DataCollectorExtensionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var DataCollectorExtension
*/
private $extension;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataCollector;
public function setUp()
{
$this->dataCollector = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface');
$this->extension = new DataCollectorExtension($this->dataCollector);
}
public function testLoadTypeExtensions()
{
$typeExtensions = $this->extension->getTypeExtensions('form');
$this->assertInternalType('array', $typeExtensions);
$this->assertCount(1, $typeExtensions);
$this->assertInstanceOf('Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension', array_shift($typeExtensions));
}
}

View File

@ -0,0 +1,465 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\DataCollector;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollector;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormView;
class FormDataCollectorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataExtractor;
/**
* @var FormDataCollector
*/
private $dataCollector;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dispatcher;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $factory;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataMapper;
/**
* @var Form
*/
private $form;
/**
* @var Form
*/
private $childForm;
/**
* @var FormView
*/
private $view;
/**
* @var FormView
*/
private $childView;
protected function setUp()
{
$this->dataExtractor = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataExtractorInterface');
$this->dataCollector = new FormDataCollector($this->dataExtractor);
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->dataMapper = $this->getMock('Symfony\Component\Form\DataMapperInterface');
$this->form = $this->createForm('name');
$this->childForm = $this->createForm('child');
$this->view = new FormView();
$this->childView = new FormView();
}
public function testBuildPreliminaryFormTree()
{
$this->form->add($this->childForm);
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
$this->dataExtractor->expects($this->at(2))
->method('extractDefaultData')
->with($this->form)
->will($this->returnValue(array('default_data' => 'foo')));
$this->dataExtractor->expects($this->at(3))
->method('extractDefaultData')
->with($this->childForm)
->will($this->returnValue(array('default_data' => 'bar')));
$this->dataExtractor->expects($this->at(4))
->method('extractSubmittedData')
->with($this->form)
->will($this->returnValue(array('submitted_data' => 'foo')));
$this->dataExtractor->expects($this->at(5))
->method('extractSubmittedData')
->with($this->childForm)
->will($this->returnValue(array('submitted_data' => 'bar')));
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectDefaultData($this->form);
$this->dataCollector->collectSubmittedData($this->form);
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'default_data' => 'foo',
'submitted_data' => 'foo',
'children' => array(
'child' => array(
'config' => 'bar',
'default_data' => 'bar',
'submitted_data' => 'bar',
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildMultiplePreliminaryFormTrees()
{
$form1 = $this->createForm('form1');
$form2 = $this->createForm('form2');
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($form1)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($form2)
->will($this->returnValue(array('config' => 'bar')));
$this->dataCollector->collectConfiguration($form1);
$this->dataCollector->collectConfiguration($form2);
$this->dataCollector->buildPreliminaryFormTree($form1);
$this->assertSame(array(
'forms' => array(
'form1' => array(
'config' => 'foo',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
$this->dataCollector->buildPreliminaryFormTree($form2);
$this->assertSame(array(
'forms' => array(
'form1' => array(
'config' => 'foo',
'children' => array(),
),
'form2' => array(
'config' => 'bar',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildSamePreliminaryFormTreeMultipleTimes()
{
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractDefaultData')
->with($this->form)
->will($this->returnValue(array('default_data' => 'foo')));
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
$this->dataCollector->collectDefaultData($this->form);
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'default_data' => 'foo',
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildPreliminaryFormTreeWithoutCollectingAnyData()
{
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'children' => array(),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testBuildFinalFormTree()
{
$this->form->add($this->childForm);
$this->view->children['child'] = $this->childView;
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
$this->dataExtractor->expects($this->at(2))
->method('extractDefaultData')
->with($this->form)
->will($this->returnValue(array('default_data' => 'foo')));
$this->dataExtractor->expects($this->at(3))
->method('extractDefaultData')
->with($this->childForm)
->will($this->returnValue(array('default_data' => 'bar')));
$this->dataExtractor->expects($this->at(4))
->method('extractSubmittedData')
->with($this->form)
->will($this->returnValue(array('submitted_data' => 'foo')));
$this->dataExtractor->expects($this->at(5))
->method('extractSubmittedData')
->with($this->childForm)
->will($this->returnValue(array('submitted_data' => 'bar')));
$this->dataExtractor->expects($this->at(6))
->method('extractViewVariables')
->with($this->view)
->will($this->returnValue(array('view_vars' => 'foo')));
$this->dataExtractor->expects($this->at(7))
->method('extractViewVariables')
->with($this->childView)
->will($this->returnValue(array('view_vars' => 'bar')));
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectDefaultData($this->form);
$this->dataCollector->collectSubmittedData($this->form);
$this->dataCollector->collectViewVariables($this->view);
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'view_vars' => 'foo',
'config' => 'foo',
'default_data' => 'foo',
'submitted_data' => 'foo',
'children' => array(
'child' => array(
'view_vars' => 'bar',
'config' => 'bar',
'default_data' => 'bar',
'submitted_data' => 'bar',
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testFinalFormReliesOnFormViewStructure()
{
$this->form->add($this->createForm('first'));
$this->form->add($this->createForm('second'));
$this->view->children['second'] = $this->childView;
$this->dataCollector->buildPreliminaryFormTree($this->form);
$this->assertSame(array(
'forms' => array(
'name' => array(
'children' => array(
'first' => array(
'children' => array(),
),
'second' => array(
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'children' => array(
// "first" not present in FormView
'second' => array(
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testChildViewsCanBeWithoutCorrespondingChildForms()
{
// don't add $this->childForm to $this->form!
$this->view->children['child'] = $this->childView;
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
// explicitly call collectConfiguration(), since $this->childForm is not
// contained in the form tree
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectConfiguration($this->childForm);
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'children' => array(
'child' => array(
// no "config" key
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testChildViewsWithoutCorrespondingChildFormsMayBeExplicitlyAssociated()
{
// don't add $this->childForm to $this->form!
$this->view->children['child'] = $this->childView;
// but associate the two
$this->dataCollector->associateFormWithView($this->childForm, $this->childView);
$this->dataExtractor->expects($this->at(0))
->method('extractConfiguration')
->with($this->form)
->will($this->returnValue(array('config' => 'foo')));
$this->dataExtractor->expects($this->at(1))
->method('extractConfiguration')
->with($this->childForm)
->will($this->returnValue(array('config' => 'bar')));
// explicitly call collectConfiguration(), since $this->childForm is not
// contained in the form tree
$this->dataCollector->collectConfiguration($this->form);
$this->dataCollector->collectConfiguration($this->childForm);
$this->dataCollector->buildFinalFormTree($this->form, $this->view);
$this->assertSame(array(
'forms' => array(
'name' => array(
'config' => 'foo',
'children' => array(
'child' => array(
'config' => 'bar',
'children' => array(),
),
),
),
),
'nb_errors' => 0,
), $this->dataCollector->getData());
}
public function testCollectSubmittedDataCountsErrors()
{
$form1 = $this->createForm('form1');
$childForm1 = $this->createForm('child1');
$form2 = $this->createForm('form2');
$form1->add($childForm1);
$this->dataExtractor->expects($this->at(0))
->method('extractSubmittedData')
->with($form1)
->will($this->returnValue(array('errors' => array('foo'))));
$this->dataExtractor->expects($this->at(1))
->method('extractSubmittedData')
->with($childForm1)
->will($this->returnValue(array('errors' => array('bar', 'bam'))));
$this->dataExtractor->expects($this->at(2))
->method('extractSubmittedData')
->with($form2)
->will($this->returnValue(array('errors' => array('baz'))));
$this->dataCollector->collectSubmittedData($form1);
$data = $this->dataCollector->getData();
$this->assertSame(3, $data['nb_errors']);
$this->dataCollector->collectSubmittedData($form2);
$data = $this->dataCollector->getData();
$this->assertSame(4, $data['nb_errors']);
}
private function createForm($name)
{
$builder = new FormBuilder($name, null, $this->dispatcher, $this->factory);
$builder->setCompound(true);
$builder->setDataMapper($this->dataMapper);
return $builder->getForm();
}
}

View File

@ -0,0 +1,338 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\DataCollector;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
class FormDataExtractorTest_SimpleValueExporter extends ValueExporter
{
/**
* {@inheritdoc}
*/
public function exportValue($value)
{
return var_export($value, true);
}
}
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var FormDataExtractorTest_SimpleValueExporter
*/
private $valueExporter;
/**
* @var FormDataExtractor
*/
private $dataExtractor;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dispatcher;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $factory;
protected function setUp()
{
$this->valueExporter = new FormDataExtractorTest_SimpleValueExporter();
$this->dataExtractor = new FormDataExtractor($this->valueExporter);
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
}
public function testExtractConfiguration()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$type->expects($this->any())
->method('getName')
->will($this->returnValue('type_name'));
$type->expects($this->any())
->method('getInnerType')
->will($this->returnValue(new \stdClass()));
$form = $this->createBuilder('name')
->setType($type)
->getForm();
$this->assertSame(array(
'type' => 'type_name',
'type_class' => 'stdClass',
'synchronized' => 'true',
'passed_options' => array(),
'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form));
}
public function testExtractConfigurationSortsPassedOptions()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$type->expects($this->any())
->method('getName')
->will($this->returnValue('type_name'));
$type->expects($this->any())
->method('getInnerType')
->will($this->returnValue(new \stdClass()));
$options = array(
'b' => 'foo',
'a' => 'bar',
'c' => 'baz',
);
$form = $this->createBuilder('name')
->setType($type)
// passed options are stored in an attribute by
// ResolvedTypeDataCollectorProxy
->setAttribute('data_collector/passed_options', $options)
->getForm();
$this->assertSame(array(
'type' => 'type_name',
'type_class' => 'stdClass',
'synchronized' => 'true',
'passed_options' => array(
'a' => "'bar'",
'b' => "'foo'",
'c' => "'baz'",
),
'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form));
}
public function testExtractConfigurationSortsResolvedOptions()
{
$type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface');
$type->expects($this->any())
->method('getName')
->will($this->returnValue('type_name'));
$type->expects($this->any())
->method('getInnerType')
->will($this->returnValue(new \stdClass()));
$options = array(
'b' => 'foo',
'a' => 'bar',
'c' => 'baz',
);
$form = $this->createBuilder('name', $options)
->setType($type)
->getForm();
$this->assertSame(array(
'type' => 'type_name',
'type_class' => 'stdClass',
'synchronized' => 'true',
'passed_options' => array(),
'resolved_options' => array(
'a' => "'bar'",
'b' => "'foo'",
'c' => "'baz'",
),
), $this->dataExtractor->extractConfiguration($form));
}
public function testExtractDefaultData()
{
$form = $this->createBuilder('name')->getForm();
$form->setData('Foobar');
$this->assertSame(array(
'default_data' => array(
'norm' => "'Foobar'",
),
'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form));
}
public function testExtractDefaultDataStoresModelDataIfDifferent()
{
$form = $this->createBuilder('name')
->addModelTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar'
)))
->getForm();
$form->setData('Foo');
$this->assertSame(array(
'default_data' => array(
'norm' => "'Bar'",
'model' => "'Foo'",
),
'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form));
}
public function testExtractDefaultDataStoresViewDataIfDifferent()
{
$form = $this->createBuilder('name')
->addViewTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar'
)))
->getForm();
$form->setData('Foo');
$this->assertSame(array(
'default_data' => array(
'norm' => "'Foo'",
'view' => "'Bar'",
),
'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form));
}
public function testExtractSubmittedData()
{
$form = $this->createBuilder('name')->getForm();
$form->submit('Foobar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foobar'",
),
'errors' => array(),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataStoresModelDataIfDifferent()
{
$form = $this->createBuilder('name')
->addModelTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar',
'' => '',
)))
->getForm();
$form->submit('Bar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Bar'",
'model' => "'Foo'",
),
'errors' => array(),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataStoresViewDataIfDifferent()
{
$form = $this->createBuilder('name')
->addViewTransformer(new FixedDataTransformer(array(
'Foo' => 'Bar',
'' => '',
)))
->getForm();
$form->submit('Bar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foo'",
'view' => "'Bar'",
),
'errors' => array(),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataStoresErrors()
{
$form = $this->createBuilder('name')->getForm();
$form->submit('Foobar');
$form->addError(new FormError('Invalid!'));
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foobar'",
),
'errors' => array(
array('message' => 'Invalid!'),
),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractSubmittedDataRemembersIfNonSynchronized()
{
$form = $this->createBuilder('name')
->addModelTransformer(new CallbackTransformer(
function () {},
function () {
throw new TransformationFailedException('Fail!');
}
))
->getForm();
$form->submit('Foobar');
$this->assertSame(array(
'submitted_data' => array(
'norm' => "'Foobar'",
'model' => 'NULL',
),
'errors' => array(),
'synchronized' => 'false',
), $this->dataExtractor->extractSubmittedData($form));
}
public function testExtractViewVariables()
{
$view = new FormView();
$view->vars = array(
'b' => 'foo',
'a' => 'bar',
'c' => 'baz',
);
$this->assertSame(array(
'view_vars' => array(
'a' => "'bar'",
'b' => "'foo'",
'c' => "'baz'",
),
), $this->dataExtractor->extractViewVariables($view));
}
/**
* @param string $name
* @param array $options
*
* @return FormBuilder
*/
private function createBuilder($name, array $options = array())
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory, $options);
}
}

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\DataCollector\Type;
use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension;
class DataCollectorTypeExtensionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var DataCollectorTypeExtension
*/
private $extension;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataCollector;
public function setUp()
{
$this->dataCollector = $this->getMock('Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface');
$this->extension = new DataCollectorTypeExtension($this->dataCollector);
}
public function testGetExtendedType()
{
$this->assertEquals('form', $this->extension->getExtendedType());
}
public function testBuildForm()
{
$builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
$builder->expects($this->atLeastOnce())
->method('addEventSubscriber')
->with($this->isInstanceOf('Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener'));
$this->extension->buildForm($builder, array());
}
}

View File

@ -46,6 +46,11 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
*/
private $resolvedTypeFactory;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $builder;
/**
* @var FormFactory
*/
@ -57,6 +62,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->guesser1 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->guesser2 = $this->getMock('Symfony\Component\Form\FormTypeGuesserInterface');
$this->registry = $this->getMock('Symfony\Component\Form\FormRegistryInterface');
$this->builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
$this->factory = new FormFactory($this->registry, $this->resolvedTypeFactory);
$this->registry->expects($this->any())
@ -70,6 +76,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateNamedBuilderWithTypeName()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
@ -80,14 +87,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', 'type', null, $options));
}
public function testCreateNamedBuilderWithTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$type = new FooType();
$resolvedType = $this->getMockResolvedType();
@ -99,14 +115,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $type, null, $options));
}
public function testCreateNamedBuilderWithTypeInstanceWithParentType()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$type = new FooSubType();
$resolvedType = $this->getMockResolvedType();
$parentResolvedType = $this->getMockResolvedType();
@ -124,14 +149,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $type, null, $options));
}
public function testCreateNamedBuilderWithTypeInstanceWithParentTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$type = new FooSubTypeWithParentInstance();
$resolvedType = $this->getMockResolvedType();
$parentResolvedType = $this->getMockResolvedType();
@ -149,28 +183,46 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $type, null, $options));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $type, null, $options));
}
public function testCreateNamedBuilderWithResolvedTypeInstance()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $resolvedType, null, $options));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', $resolvedType, null, $options));
}
public function testCreateNamedBuilderFillsDataOption()
{
$givenOptions = array('a' => '1', 'b' => '2');
$expectedOptions = array_merge($givenOptions, array('data' => 'DATA'));
$resolvedOptions = array('a' => '2', 'b' => '3', 'data' => 'DATA');
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
@ -181,14 +233,23 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $expectedOptions)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $givenOptions));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', 'type', 'DATA', $givenOptions));
}
public function testCreateNamedBuilderDoesNotOverrideExistingDataOption()
{
$options = array('a' => '1', 'b' => '2', 'data' => 'CUSTOM');
$resolvedOptions = array('a' => '2', 'b' => '3', 'data' => 'CUSTOM');
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
@ -199,9 +260,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue('BUILDER'));
->will($this->returnValue($this->builder));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', 'DATA', $options));
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->assertSame($this->builder, $this->factory->createNamedBuilder('name', 'type', 'DATA', $options));
}
/**
@ -216,8 +285,8 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateUsesTypeNameIfTypeGivenAsString()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$builder = $this->getMockFormBuilder();
$this->registry->expects($this->once())
->method('getType')
@ -227,9 +296,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'TYPE', $options)
->will($this->returnValue($builder));
->will($this->returnValue($this->builder));
$builder->expects($this->once())
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->builder->expects($this->once())
->method('getForm')
->will($this->returnValue('FORM'));
@ -239,8 +316,8 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateUsesTypeNameIfTypeGivenAsObject()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$builder = $this->getMockFormBuilder();
$resolvedType->expects($this->once())
->method('getName')
@ -249,9 +326,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'TYPE', $options)
->will($this->returnValue($builder));
->will($this->returnValue($this->builder));
$builder->expects($this->once())
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->builder->expects($this->once())
->method('getForm')
->will($this->returnValue('FORM'));
@ -261,8 +346,8 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
public function testCreateNamed()
{
$options = array('a' => '1', 'b' => '2');
$resolvedOptions = array('a' => '2', 'b' => '3');
$resolvedType = $this->getMockResolvedType();
$builder = $this->getMockFormBuilder();
$this->registry->expects($this->once())
->method('getType')
@ -272,9 +357,17 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options)
->will($this->returnValue($builder));
->will($this->returnValue($this->builder));
$builder->expects($this->once())
$this->builder->expects($this->any())
->method('getOptions')
->will($this->returnValue($resolvedOptions));
$resolvedType->expects($this->once())
->method('buildForm')
->with($this->builder, $resolvedOptions);
$this->builder->expects($this->once())
->method('getForm')
->will($this->returnValue('FORM'));
@ -294,9 +387,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array())
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
$this->builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
@ -326,9 +419,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'password', null, array('max_length' => 7))
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
$this->builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderCreatesTextFormIfNoGuess()
@ -345,9 +438,9 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text')
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
$this->builder = $factory->createBuilderForProperty('Application\Author', 'firstName');
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
public function testOptionsCanBeOverridden()
@ -368,14 +461,14 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('max_length' => 11))
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty(
$this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName',
null,
array('max_length' => 11)
);
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderUsesMaxLengthIfFound()
@ -403,12 +496,12 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('max_length' => 20))
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty(
$this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName'
);
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderUsesRequiredSettingWithHighestConfidence()
@ -436,12 +529,12 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('required' => false))
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty(
$this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName'
);
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
public function testCreateBuilderUsesPatternIfFound()
@ -469,12 +562,12 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
->with('firstName', 'text', null, array('pattern' => '[a-zA-Z]'))
->will($this->returnValue('builderInstance'));
$builder = $factory->createBuilderForProperty(
$this->builder = $factory->createBuilderForProperty(
'Application\Author',
'firstName'
);
$this->assertEquals('builderInstance', $builder);
$this->assertEquals('builderInstance', $this->builder);
}
private function getMockFactory(array $methods = array())
@ -494,9 +587,4 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
{
return $this->getMock('Symfony\Component\Form\FormTypeInterface');
}
private function getMockFormBuilder()
{
return $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
}
}

View File

@ -36,12 +36,71 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $dataMapper;
private $parentType;
private $type;
private $extension1;
private $extension2;
private $parentResolvedType;
private $resolvedType;
protected function setUp()
{
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->dataMapper = $this->getMock('Symfony\Component\Form\DataMapperInterface');
$this->parentType = $this->getMockFormType();
$this->type = $this->getMockFormType();
$this->extension1 = $this->getMockFormTypeExtension();
$this->extension2 = $this->getMockFormTypeExtension();
$this->parentResolvedType = new ResolvedFormType($this->parentType);
$this->resolvedType = new ResolvedFormType($this->type, array($this->extension1, $this->extension2), $this->parentResolvedType);
}
public function testGetOptionsResolver()
{
if (version_compare(\PHPUnit_Runner_Version::id(), '3.7', '<')) {
$this->markTestSkipped('This test requires PHPUnit 3.7.');
}
$test = $this;
$i = 0;
$assertIndexAndAddOption = function ($index, $option, $default) use (&$i, $test) {
return function (OptionsResolverInterface $resolver) use (&$i, $test, $index, $option, $default) {
/* @var \PHPUnit_Framework_TestCase $test */
$test->assertEquals($index, $i, 'Executed at index '.$index);
++$i;
$resolver->setDefaults(array($option => $default));
};
};
// First the default options are generated for the super type
$this->parentType->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(0, 'a', 'a_default')));
// The form type itself
$this->type->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(1, 'b', 'b_default')));
// And its extensions
$this->extension1->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(2, 'c', 'c_default')));
$this->extension2->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(3, 'd', 'd_default')));
$givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
$resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
$resolver = $this->resolvedType->getOptionsResolver();
$this->assertEquals($resolvedOptions, $resolver->resolve($givenOptions));
}
public function testCreateBuilder()
@ -50,13 +109,70 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('This test requires PHPUnit 3.7.');
}
$parentType = $this->getMockFormType();
$type = $this->getMockFormType();
$extension1 = $this->getMockFormTypeExtension();
$extension2 = $this->getMockFormTypeExtension();
$givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
$resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
$optionsResolver = $this->getMock('Symfony\Component\OptionsResolver\OptionsResolverInterface');
$parentResolvedType = new ResolvedFormType($parentType);
$resolvedType = new ResolvedFormType($type, array($extension1, $extension2), $parentResolvedType);
$this->resolvedType = $this->getMockBuilder('Symfony\Component\Form\ResolvedFormType')
->setConstructorArgs(array($this->type, array($this->extension1, $this->extension2), $this->parentResolvedType))
->setMethods(array('getOptionsResolver'))
->getMock();
$this->resolvedType->expects($this->once())
->method('getOptionsResolver')
->will($this->returnValue($optionsResolver));
$optionsResolver->expects($this->once())
->method('resolve')
->with($givenOptions)
->will($this->returnValue($resolvedOptions));
$factory = $this->getMockFormFactory();
$builder = $this->resolvedType->createBuilder($factory, 'name', $givenOptions);
$this->assertSame($this->resolvedType, $builder->getType());
$this->assertSame($resolvedOptions, $builder->getOptions());
$this->assertNull($builder->getDataClass());
}
public function testCreateBuilderWithDataClassOption()
{
if (version_compare(\PHPUnit_Runner_Version::id(), '3.7', '<')) {
$this->markTestSkipped('This test requires PHPUnit 3.7.');
}
$givenOptions = array('data_class' => 'Foo');
$resolvedOptions = array('data_class' => '\stdClass');
$optionsResolver = $this->getMock('Symfony\Component\OptionsResolver\OptionsResolverInterface');
$this->resolvedType = $this->getMockBuilder('Symfony\Component\Form\ResolvedFormType')
->setConstructorArgs(array($this->type, array($this->extension1, $this->extension2), $this->parentResolvedType))
->setMethods(array('getOptionsResolver'))
->getMock();
$this->resolvedType->expects($this->once())
->method('getOptionsResolver')
->will($this->returnValue($optionsResolver));
$optionsResolver->expects($this->once())
->method('resolve')
->with($givenOptions)
->will($this->returnValue($resolvedOptions));
$factory = $this->getMockFormFactory();
$builder = $this->resolvedType->createBuilder($factory, 'name', $givenOptions);
$this->assertSame($this->resolvedType, $builder->getType());
$this->assertSame($resolvedOptions, $builder->getOptions());
$this->assertSame('\stdClass', $builder->getDataClass());
}
public function testBuildForm()
{
if (version_compare(\PHPUnit_Runner_Version::id(), '3.7', '<')) {
$this->markTestSkipped('This test requires PHPUnit 3.7.');
}
$test = $this;
$i = 0;
@ -70,169 +186,142 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
};
};
$assertIndexAndAddOption = function ($index, $option, $default) use ($assertIndex) {
$assertIndex = $assertIndex($index);
$options = array('a' => 'Foo', 'b' => 'Bar');
$builder = $this->getMock('Symfony\Component\Form\Test\FormBuilderInterface');
return function (OptionsResolverInterface $resolver) use ($assertIndex, $index, $option, $default) {
$assertIndex();
$resolver->setDefaults(array($option => $default));
};
};
// First the default options are generated for the super type
$parentType->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(0, 'a', 'a_default')));
// The form type itself
$type->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(1, 'b', 'b_default')));
// And its extensions
$extension1->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(2, 'c', 'c_default')));
$extension2->expects($this->once())
->method('setDefaultOptions')
->will($this->returnCallback($assertIndexAndAddOption(3, 'd', 'd_default')));
$givenOptions = array('a' => 'a_custom', 'c' => 'c_custom');
$resolvedOptions = array('a' => 'a_custom', 'b' => 'b_default', 'c' => 'c_custom', 'd' => 'd_default');
// Then the form is built for the super type
$parentType->expects($this->once())
// First the form is built for the super type
$this->parentType->expects($this->once())
->method('buildForm')
->with($this->anything(), $resolvedOptions)
->will($this->returnCallback($assertIndex(4)));
->with($builder, $options)
->will($this->returnCallback($assertIndex(0)));
// Then the type itself
$type->expects($this->once())
$this->type->expects($this->once())
->method('buildForm')
->with($this->anything(), $resolvedOptions)
->will($this->returnCallback($assertIndex(5)));
->with($builder, $options)
->will($this->returnCallback($assertIndex(1)));
// Then its extensions
$extension1->expects($this->once())
$this->extension1->expects($this->once())
->method('buildForm')
->with($this->anything(), $resolvedOptions)
->will($this->returnCallback($assertIndex(6)));
->with($builder, $options)
->will($this->returnCallback($assertIndex(2)));
$extension2->expects($this->once())
$this->extension2->expects($this->once())
->method('buildForm')
->with($this->anything(), $resolvedOptions)
->will($this->returnCallback($assertIndex(7)));
->with($builder, $options)
->will($this->returnCallback($assertIndex(3)));
$factory = $this->getMockFormFactory();
$builder = $resolvedType->createBuilder($factory, 'name', $givenOptions);
$this->assertSame($resolvedType, $builder->getType());
$this->resolvedType->buildForm($builder, $options);
}
public function testCreateView()
{
$parentType = $this->getMockFormType();
$type = $this->getMockFormType();
$field1Type = $this->getMockFormType();
$field2Type = $this->getMockFormType();
$extension1 = $this->getMockFormTypeExtension();
$extension2 = $this->getMockFormTypeExtension();
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$parentResolvedType = new ResolvedFormType($parentType);
$resolvedType = new ResolvedFormType($type, array($extension1, $extension2), $parentResolvedType);
$field1ResolvedType = new ResolvedFormType($field1Type);
$field2ResolvedType = new ResolvedFormType($field2Type);
$view = $this->resolvedType->createView($form);
$this->assertInstanceOf('Symfony\Component\Form\FormView', $view);
$this->assertNull($view->parent);
}
public function testCreateViewWithParent()
{
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$parentView = $this->getMock('Symfony\Component\Form\FormView');
$view = $this->resolvedType->createView($form, $parentView);
$this->assertInstanceOf('Symfony\Component\Form\FormView', $view);
$this->assertSame($parentView, $view->parent);
}
public function testBuildView()
{
$options = array('a' => '1', 'b' => '2');
$form = $this->getBuilder('name', $options)
->setCompound(true)
->setDataMapper($this->dataMapper)
->setType($resolvedType)
->add($this->getBuilder('foo')->setType($field1ResolvedType))
->add($this->getBuilder('bar')->setType($field2ResolvedType))
->getForm();
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$view = $this->getMock('Symfony\Component\Form\FormView');
$test = $this;
$i = 0;
$assertIndexAndNbOfChildViews = function ($index, $nbOfChildViews) use (&$i, $test) {
return function (FormView $view) use (&$i, $test, $index, $nbOfChildViews) {
$assertIndex = function ($index) use (&$i, $test) {
return function () use (&$i, $test, $index) {
/* @var \PHPUnit_Framework_TestCase $test */
$test->assertEquals($index, $i, 'Executed at index '.$index);
$test->assertCount($nbOfChildViews, $view);
++$i;
};
};
// First the super type
$parentType->expects($this->once())
$this->parentType->expects($this->once())
->method('buildView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(0, 0)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(0)));
// Then the type itself
$type->expects($this->once())
$this->type->expects($this->once())
->method('buildView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(1, 0)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(1)));
// Then its extensions
$extension1->expects($this->once())
$this->extension1->expects($this->once())
->method('buildView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(2, 0)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(2)));
$extension2->expects($this->once())
$this->extension2->expects($this->once())
->method('buildView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(3, 0)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(3)));
// Now the first child form
$field1Type->expects($this->once())
->method('buildView')
->will($this->returnCallback($assertIndexAndNbOfChildViews(4, 0)));
$field1Type->expects($this->once())
$this->resolvedType->buildView($view, $form, $options);
}
public function testFinishView()
{
$options = array('a' => '1', 'b' => '2');
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$view = $this->getMock('Symfony\Component\Form\FormView');
$test = $this;
$i = 0;
$assertIndex = function ($index) use (&$i, $test) {
return function () use (&$i, $test, $index) {
/* @var \PHPUnit_Framework_TestCase $test */
$test->assertEquals($index, $i, 'Executed at index '.$index);
++$i;
};
};
// First the super type
$this->parentType->expects($this->once())
->method('finishView')
->will($this->returnCallback($assertIndexAndNbOfChildViews(5, 0)));
// And the second child form
$field2Type->expects($this->once())
->method('buildView')
->will($this->returnCallback($assertIndexAndNbOfChildViews(6, 0)));
$field2Type->expects($this->once())
->method('finishView')
->will($this->returnCallback($assertIndexAndNbOfChildViews(7, 0)));
// Again first the parent
$parentType->expects($this->once())
->method('finishView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(8, 2)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(0)));
// Then the type itself
$type->expects($this->once())
$this->type->expects($this->once())
->method('finishView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(9, 2)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(1)));
// Then its extensions
$extension1->expects($this->once())
$this->extension1->expects($this->once())
->method('finishView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(10, 2)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(2)));
$extension2->expects($this->once())
$this->extension2->expects($this->once())
->method('finishView')
->with($this->anything(), $form, $options)
->will($this->returnCallback($assertIndexAndNbOfChildViews(11, 2)));
->with($view, $form, $options)
->will($this->returnCallback($assertIndex(3)));
$parentView = new FormView();
$view = $resolvedType->createView($form, $parentView);
$this->assertSame($parentView, $view->parent);
$this->resolvedType->finishView($view, $form, $options);
}
/**

View File

@ -11,17 +11,26 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporterInterface;
/**
* DataCollector.
*
* Children of this class must store the collected data in the data property.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@symfony.com>
*/
abstract class DataCollector implements DataCollectorInterface, \Serializable
{
protected $data;
/**
* @var ValueExporter
*/
private $valueExporter;
public function serialize()
{
return serialize($this->data);
@ -41,35 +50,10 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
*/
protected function varToString($var)
{
if (is_object($var)) {
return sprintf('Object(%s)', get_class($var));
if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter();
}
if (is_array($var)) {
$a = array();
foreach ($var as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->varToString($v));
}
return sprintf("Array(%s)", implode(', ', $a));
}
if (is_resource($var)) {
return sprintf('Resource(%s)', get_resource_type($var));
}
if (null === $var) {
return 'null';
}
if (false === $var) {
return 'false';
}
if (true === $var) {
return 'true';
}
return (string) $var;
$this->valueExporter->exportValue($var);
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\DataCollector\Util;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValueExporter
{
/**
* Converts a PHP value to a string.
*
* @param mixed $value The PHP value
*
* @return string The string representation of the given value
*/
public function exportValue($value)
{
if (is_object($value)) {
return sprintf('Object(%s)', get_class($value));
}
if (is_array($value)) {
$a = array();
foreach ($value as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->exportValue($v));
}
return sprintf("Array(%s)", implode(', ', $a));
}
if (is_resource($value)) {
return sprintf('Resource(%s)', get_resource_type($value));
}
if (null === $value) {
return 'null';
}
if (false === $value) {
return 'false';
}
if (true === $value) {
return 'true';
}
return (string) $value;
}
}