Hi dear Symfony2 devs, I was pushed by several people to refactor the Form component to be more decoupled and make better use of DI. Although I wasn't really convinced of this approach at first, I decided to give it a try - and am pretty satisfied with the results. Most of the features have been refactored, but there are still details that need to be worked on. You can find the current progress here:
https://github.com/bschussek/symfony/tree/experimental Important: This is experimental. It is not decided whether this will ever be merged into master. I also don't recommend to use this in any application yet as CSRF protection is not ported yet. Thanks to Bulat for helping me. This post will outline the problems this refactoring tries to fix and the benefits that we gain. Also, we need to find out how to deal with the RC that is planned for next week. Fixed problems ---------------------- * The option implementation of Fields/Forms was buggy * Forms could not be put into the DIC and retrieved from there * Stubbing/mocking of Forms in controllers was impossible, because they were created with "new" * Dependencies where hidden inside configure() and could not be replaced * Dependencies required in a field had to manually be added as options of all parent forms (e.g. EntityManager) * Field/Form by design contained lots of code that was relevant for rendering only (getId(), getName(), getPattern() etc.) * Because of the monolithic inheritance structure, it wasn't possible to create fields that inherit the rendering of for example TextField but the behaviour of another field * Rendering was unflexible. It wasn't possible to override for example the "row" template for a specific field type (needed for RepeatedField, inline forms etc.) Additional advantages -------------------------------- * Fields and their renderers are now completely separated * Rendering is more concise and flexible * All fields of a specific type (e.g. text fields) used in an application can be overridden/extended (probably more that I forgot right now) I want to explain how form creation and form rendering work with the changed approach. Note that not all of the stuff I'm explaining here is complete yet. Form handling --------------------- To create a form, you implement a form factory in your bundle. This factory could look like this: https://gist.github.com/842827 Binding will look like this: https://gist.github.com/843094 And rendering something like this: https://gist.github.com/843115 Rendering improvements ------------------------------------ The most interesting part I believe is the rendering. As you can see, we passed $form->getRenderer() to the template. This renderer encloses variables and methods that are available there. Example variables are "id", "name", "value", "class" etc. The methods are "errors", "widget", "label", "row" and "rest". Each field and form has exactly one renderer. Depending on the field type, this renderer has additional variables set that can be used in the template. The renderer of a money field, for example, offers the additional variable "money_pattern". The renderer of a form offers "fields", "visible_fields" etc. All these variables can either be statically set on the renderer by calling setVar(), or dynamically using renderer plugins. They can also be overridden when calling individual methods of the renderer, like "widget". In form themes, these variables are available as global variables in the blocks of the field: {% block money_field %} {{ money_pattern }} {% endblock %} In normal templates, you can access the variables as properties of the field renderer: {# Assuming the field "price" is a money field #} {{ form.price.money_pattern }} As said above, each renderer also offers rendering methods. With {{ field.widget }} for example, you can output the widget of a field. You can also override any of the variables in the call. {{ field.wigdet({ 'money_pattern': '...' }) }} Unlike before, there is no difference between HTML attributes and template variables anymore. Each HTML attribute that should be settable must be available as variable, for example: {{ field.wigdet({ 'class': 'myclass' }) }} {% block money_field %} <div class="{{ class }}"> etc. Because renderers are simple PHP objects, you can extend them and add functionality. You could implement this renderer https://gist.github.com/843139 and create a new form theme with a "help" block. You can then call {{ field.help('My help text') }} on all fields that have your custom renderer set. It is also possible to override the renderers of all core fields to add functionality globally. An interesting default rendering method is "rest" (not implemented yet, if you have better names tell me). Because each field has a stateful renderer, we can track which field was rendered at least once. With {{ form.rest }} you can render all fields that weren't explicitely rendered. This replaces the "hidden" helper and solves lots of other problems. If you, for example, forgot to render the hidden fields in a subform, these will be rendered here. If you added a field in the PHP code, but not in your template, it will be rendered here. The designer will notice the problem and manually render the field correctly. What about PHP rendering? ---------------------------------------- So far I've only talked about Twig. All renderers are connected to a theme (an instance of ThemeInterface). By default, the component ships with TwigTheme. By exchanging this object, you can either change the template(s) used as themes or the whole theming engine. You could implement a SmartyTheme that renders form fields using Smarty. Note that the theming engine of the form and the templating engine where you render the form are independent! For example, currently there is only a TwigTheme, but you can render it in PHP template nevertheless. Just pass the form renderer to the template and use it like in Twig: <?php echo $form->getWidget() ?> <?php foreach ($form['visible_fields'] as $field): ?> etc. I will cut this post for now. See my implementation if you want to learn more. Conclusion ---------------- Because of DI, lots of problems in the form architecture could be fixed and the rendering could be significantly improved. The reason for this is that Twig and other dependencies can now be injected. Of course, there are problems too: The biggest one is that RC1 is planned for next week. Most of the existing unit tests are green again, but there are still lots of smaller things that need to be fixed/worked on, and this won't be done next week. I am very confident though that this refactoring can be finished very soon. Most of the existing features have been ported, for the remaining ones (FieldFactoryGuesser, CSRF protection and CollectionField) I don't see any showstoppers - it just needs to be done. What I need: ------------------ * to know whether you want to see this in the first Symfony2 release. Delivering it in later releases will be difficult because this is a major BC break * input. I have considered a lot of feedback in this refactoring that I have got lately, but more input is always better. * help. If you have time to help, you are very welcome. Best wishes, Bernhard -- Software Architect & Engineer Blog: http://webmozarts.com Twitter: http://twitter.com/webmozart -- If you want to report a vulnerability issue on symfony, please send it to security at symfony-project.com You received this message because you are subscribed to the Google Groups "symfony developers" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/symfony-devs?hl=en
