I'm back! There were a few repeat presentations from San Francisco, coupled with a lack of functional power outlets or wifi and an overcrowded room for the second track of presentations I hadn't seen before. So I went back to the hotel and
fixed an Apostrophe bug relating to internationalization, since that is a hot-button issue for the Paris attendees.
Then I almost fell asleep on the comfy hotel couch... jet lag is dangerous!
Now it's time for Bernhard to regale us on the topic of forms in Symfony 2. To be followed by Fabien's much-anticipated keynote speech. I'll be liveblogging both. One good way to stay awake (: I managed to get on the wifi and have the brightness allll the way down. We'll see if I make it through two hours.
Bernhard Schussek
Leveraging Symfony2 Forms
Software architect in Vienna. Audience clapping for slick dude headshot slide.
"Student of software engineering."
Symfony since 2006.
Agenda: introductory example, the form config class, form processing, fields, useful features
Evolution of symfony 1 forms. 2.5 years of development.
Fixes most of its problems: embedded forms especially. Also made widgets more reusable (I thought that part worked okay, but embedded forms, yipes, broken yeah).
Referenced quote about simplicity and its long term payoff from earlier presentation today.
Service Oriented Architecture
Applications provide services
Services are interchangeable
Can be consumed by different actors: humans, machines (tests, APIs), etc.
Forms don't contain business logic. Not at all. Services do.
Forms are a means of communication between human and machine, period
Twitter is @webmozart
Example: online sausage shop
You: request /sausages/order. Server: here are some sausages...
Consumer might be a dog, or an intermediary shop reselling to dogs...
class Order
{
function setName($name);
function setAddress($address);
}
Flow of information
The dog know's he's Max. Somehow $order->setName('max') has to happen.
The order form facilitates this. A form renders fields (name, address, amount) and calls setters on some service or entity - some PHP object. It doesn't know what they do
In reverse: the form calls getters on the provided object.
The Form Config class
There is a config class for each form
class OrderFormConfig extends AbstractConfig
{
public function configure(FieldInterface $form, array $options)
{
$form->add('text', 'name')->add('integer', 'amount');
}
}
"Why not a simple OrderForm class like in Symfony 1?"
OrderForm cannot be put into the Dependency Injection Container, but OrderFormConfig can. (Why and why not?)
<service id="form.order" class="OrderFormConfig">
<tag name="form.config" alias="form.order" />
<argument type="service" id="form.factory" />
</service>
public function getIdentifier()
{
return 'form.order';
}
(Yipes. Seems like a lot for the minimum requirements of a form.)
Workflow:
$factory = $this->get('form.factory');
$form = $factory->getInstance('form.order');
$form->setData($order); // Object with getters and setters
if (post) { $form->bindReQuest($request); } // calls setters
if ($form->isValid()) { $order->send(); redirect }
Rendering can be as simple as {{ form.widget }} in Twig, but you typically template it out more
{{ form.errors }}
{% for field in form.vars.fields %}
{{ field.errors }}
{{ field.label }}
{field.widget }}
{% endfor %}
{{ form.rest }}
Etc.
(What is form.rest? Does that give us fields we didn't iterate over? Yes!)
So you might output six fields manually and then the rest. And it also outputs the hidden fields.
(What if you didn't want to render some fields? Well, probably you should have removed them from the form object entirely.)
What about validation?
Validation constraints
Note: annotations have to be in comments, I am typing them naked to save time.
This is the entity class, not the form class. (So how does validation find its way back to the form?)
class Order
{
@check:NotNull
@check:AssertType("string")
@check:MaxLength(50, message="Long name, dude...")
private $name;
}
Text input
$form->add('text', 'title');
$form->add('textarea', 'content');
$form->add('date', 'publishAt');
The date selector is nicely localized in Symfony 2. (And easier to override with a DHTML gadget, I gather.)
Country selector
$form->add('file', 'profilePicture');
Uploaded files are sticky on errors! (This is nice and replaces aValidatorFileUpload. How about preview for image uploads that are not in error although other fields are?)
$form->add('repeated', 'email');
Core fields
entity, birthday file, checkbox, hidden, choice, integer, collection, language, country, locale, date, money, datetime, number, password, percent, repeated, textarea, text, timezone, url
The url field is smarter and understands what to supply automatically.
Field architecture
Filters
Value transformers
Filters modify a value, unidrectionally. Example: FixUrlProtocolFilter.
symfony-project.com -> http://symfony-project.com
(We do this with validators in Symfony 1, I showed an example earlier)
Value transformers
Bidrectional. Converts between two representations.
DateTimeToArrayTransformer
object(DateTime) <--> array('year' => 2011, 'month' => 5, 'day' => 1)
Example: entity field
Users sees checkboxes:
x Symfony * security * validator x container
Symfony sees:
Entity field with four Checkbox fields, with their checkbox values (0, 1, 2, 3)
The output then goes through ChoicesToArrayTransformer, mapping to a hash of checkbox ids pointing to boolean values, and then to ArrayToEntitiesTransformer, which creates an ArrayCollection (?) of just the items where the boolean is true.
CSRF protection
Protection built in, as in Symfony 1.
Field creation
Manual and automatic. Symfony2 can look at domainc lass metadata (entity class metadata?) to guess the field type and settings. For example Validator metadata and Doctrine2 metadata.
You could be verbose and write:
$form->add('entity', 'sausage', array('class' => 'Sausage');
But Doctrine already knows that sausage is a one-to-one relationship to the Sausage class in the entity class associated with the form.
$form->setDataClass('Order');
$form->add('sausage');
Done.
However we can override defaults of automatic field creation when adding the field:
$form->add('sausage', array('required' => false));
Symfony 2 forms and fields implement the same interface (FieldInterface). So a form is a field. You can nest them at will.
Embedding a form:
$form->add('form.sausage', 'sausage');
That identifier has to be associated with SausageFormConfig in your services.yml.
One-to-many:
$form->add('collection', 'sausages', array('identifier' => 'form.sausage'));
"It is possible to hook JavaScript in to add new rows and remove rows and the collection will notice that" !! Very nice
Not sure exactly how that works for adding a mix of existing related objects and new related objects.
Dynamic inheritance can be implemented by implementing getParent in your config class. Forms and fields can inherit each other. EntityChoice fields inherit from the normal Choice field even though they do not do so via PHP class inheritance. (Decorator pattern)
Form themes are normal Twig templates. Blocks for each field type.
{% block textarea__widget %}
<textarea {{ block('attributes') }}>
{{ value }}
</textarea>
{% endblock textarea__widget %}
There are two themes in the core, div_layout and table_layout. Configuration in the DI parameter form.theme.template. "More flexible configuration options coming soon." [I would like to see a theme that emphasizes CSS extensibility, with no naked bits of text that can't be targeted by a class. I lost this argument for Symfony 2 core but the nice thing about Symfony 2 is that I can easily do it via a bundle and offer that to others.]
Questions a little hard to hear. He's working on the javascript plugin.
"What about useField()?" "That was a hack. It doesn't exist in Symfony 2. If you add a new property to a class it doesn't show up in the form unless the form actually calls add()"
Q. "When do you expect to merge your experimental branch back to symfony master and update the documentation?" A. "As soon as possible. Within the next week."
Q. [My question] "Can the javascript support for adding and removing related items handle a mix of existing and new items?" A. No, it does not handle existing items because the AJAX is difficult etc. [Seems achievable to me, I guess it could be a bundle]
Q. about HTML 5 support. A. There is "some."
Q. What about the Intl extension requirement? A. Eric (?) is working on it. Stub implementation should solve dependency problem. (I am hoping to contribute to this. I actually wrote a first pass but need to understand better what the expectations are and how to integrate with testing)
WAYS TO HELP: he is "searching for someone to implement a multiple file upload field," and also templates in PHP rather than Twig.
(A good talk. I need to see some good examples of this FormConfig stuff being worth the trouble.)
Next up Fabien!
Fabien Potencier
Keynote
@fabpot is getting underway
"How many think Symfony 2 is too complex?"
[No hands, crowd not quite tuned in yet]
"OK, I'm done then, sorry"
"The problem is that Symfony 2 is not stable yet, we don't have all the documentation we should, but we're working on it. We got a bunch of new documents last week. I want to talk about Symfony 2, why I won't release it today or tomorrow or next week, and why I think that's great news for the project. I also want to do some demonstration on steps that are not well-documented and things that are documented but you probably don't know how to do them"
"I'll demo why I think Twig is better than PHP, and why annotations are good"
"Let's start with some code"
Default directory structure: app, bin, src, vendor, web. "You can change it"
In Symfony 2 we still have the conventions we had in Symfony 1, but you can change them
(I notice the data folder is gone. Probably its equivalents have all been sucked into the relevant bundles)
Showing the app kernel. "Most of the time the defaults are good enough."
Showing the config files. xml, yml, php
There is also a parameters.ini with stuff you are really likely to change: database_driver, database_host, mailer_transport, mailer_user, locale, csrf_secret. Nice addition
We use namespaces everyone in Symfony 2, all code is namespaced and follows PSR0 convention, which means each namespace is a directory name, so \Acme\Blog\Author lives in src/Acme/Blog/Author.php
This is the sample he showed us in San Francisco but he is picking up where he left off somewhat, not writing the setters and getters live (:
"Business logic should not be tied to the framework you are using." In Symfony 2 all code is in bundles, the core framework is a bundle, Doctrine integration is a bundle. If I want to create a blog I create a blog bundle. Bundles are first class citizens, unlike Symfony 1 plugins. A bundle is "just a namespace, nothing more" (well, there is a bundle class too)
namespace Acme\BlogBundle;
class AcmeBlogBundle extends Bundle; (I left out a lengthy import here)
Bundles can be stored anywhere, your registerBundles() method in AppKernel.php decides what bundles are actually active.
Actions still typically correspond to (Twig) templates.
In Symfony 1 you must first add a route in routing.yml, then write an action, then write a template.
In Symfony 2, you *can* write:
public function welcomeAction()
{
return new Response('Hello Symfony2!');
}
Although typically you would use a template.
"Okay, so it doesn't work! But I can do a symfony cc..." rm -rf app/cache/*
[Crowd goes wild]
"We will have a clear cache task, but it will be different because the Symfony 1 cache takes a long time to clear and blocks many users." (Earlier it seemed there would never be a need to manually clear the cache in Symfony 2, but of course stuff always comes up...)
"The better way is to move the old one and let Symfony create a new one and then take your time removing the old one" (Like the "just change a symlink" deployment strategy)
[Something just isn't quite working.] "OK, I know what to do. You will trust me."
"Unable to find the controller for /create, check your route configuration"
"I made a mistake. I updated the Symfony code to the latest one, which was a really bad idea"
HA! DocumentRoot was pointing to some other project. An audience member nailed it. vi follies ensued
"Hello Symfony2!" is finally on the screen! Victory!
You can preface all routes for individual actions that were created with annotations like:
@extra:Route("/welcome")
By setting an annotation at the class level:
@extra:Route("/blog")
You can describe parameters in URLs with annotations:
@extra:Route("/post/{id}")
public function viewAction($id)
{
$post = whatever...
}
But better:
public function viewAction(Post $post)
{
return new Response('Post ' . $post->getTitle());
}
That works because there is an annotation listener (?) in Doctrine that spots Doctrine entities in the arguments of actions and translates recognized columns to objects of that class.
You can specify the use of templates in a non-verbose way:
@extra:Template()
You can specify a particular template:
@extra:Route('/create', name="create_post")
@extra:Template("AcmeBlogBundle:Post:new.reg.twig")
More great annotations:
@extra:Route("/post/{id}", name="blog_post")
@extra:Cache(maxage="600")
Q. Where is the responsibility to issue a 404 handled when these lookups fail?
A. "I forget, but you can say $post = null so if it does not exist then your code is prepared to deal with null and we don't do it for you" (not clear if this works yet)
Mentions the option of compiling your routes to Apache rewrite rules since Apache is much faster
Some confusion about the meaning of the Route annotation and the fact that it doesn't function like a relative path, it's appending components.
The big news is that
Symfony 2 will not be declared stable at the conference or this week, there is too much still going on.
All in all the functionality seems to work as advertised when you are not coding live in front of hundreds of people (:
"When will we release a release candidate? The goal was today or tomorrow or after the hacking day. We won't. First we are not ready yet. Why? A huge number of people are contributing now while a year ago it was just me. Fixing bugs, having discussions about the architecture of Symfony 2, how to do things the right way. Also because this is the foundation of the 2.x version so we will have the same architecture for five years perhaps. We still have a few things to discuss about, the form framework is one of them. Very important to make sure the architecture is the right one."
"The good news is that we have so many people involved that it would be a shame to discard their ideas."
"200 pull requests in just three weeks that I've actually merged into the code"
"We haven't broken backwards compatibility for perhaps two weeks"
Q. When do the annotations get compiled / do annotations slow down my code / does it matter if I use YML vs PHP / etc.?
A. It makes no difference. It all compiles to PHP (in app/cache)
"If you want to contribute we have documentation on how to contribute to the code on the website, if you don't really know git that well, we have an explanation of how to fork and branch and send pull requests. Crazy."
Questions about hiphop, Facebook's PHP-to-C++ compiler. "No, it is not PHP 5.3 compatible." "But will you make it compatible if they fix that?" "No. I don't care. It's very hard to use. It probably matters if you are Facebook. Is your site as big as Facebook? HTTP cache is a much bigger improvement. For the average user, 50% (CPU time) improvement is nothing. Configure Varnish properly." (And APC!)
Tomorrow he'll be illuminating us about the glories of HTTP cache, in particular the ability to cache small parts of a page in conjunction with intermediate proxy servers, including Varnish and a built-in fallback implementation.
Q. "One of the main goals was to reduce the magic. I was in the training yesterday. $this->get('orm.entity_manager') returns something that is not typesafe. I have no idea what it is. It feels like magic. Are there plans to have convenience functions so I have interface-safe methods?"
A. "It's not about magic really, it's about scope. Killing the magic means everything is explicit. We don't use __get, __set. None of that in Symfony 2 because it is a nightmare to debug. When you get the service there is no typing because you can't know what class it is (because the point is to offer multiple implementations)." The controllers use ->get, nothing else has to (?). (Some of this involves issues I haven't really made contact with yet)
That's it for today! Thanks for reading.