Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webmozart/413724 to your computer and use it in GitHub Desktop.
Save webmozart/413724 to your computer and use it in GitHub Desktop.
<?php
use Symfony\Components\Form\Form;
use Symfony\Components\Form\FieldGroup;
use Symfony\Components\Form\ChoiceField;
use Symfony\Components\Form\TextField;
use Symfony\Components\Form\CheckboxField;
use Symfony\Components\Form\NumberField;
use Symfony\Components\Form\PasswordField;
use Symfony\Components\Form\DoubleTextField;
use Symfony\Components\Validator\Validator;
use Symfony\Components\Validator\ConstraintValidatorFactory;
use Symfony\Components\Validator\Constraints\Min;
use Symfony\Components\Validator\Constraints\Max;
use Symfony\Components\Validator\Constraints\MinLength;
use Symfony\Components\Validator\Constraints\MaxLength;
use Symfony\Components\Validator\Constraints\AssertType;
use Symfony\Components\Validator\Constraints\Email;
use Symfony\Components\Validator\Constraints\Choice;
use Symfony\Components\Validator\Constraints\Valid;
use Symfony\Components\Validator\Mapping\ClassMetadataFactory;
use Symfony\Components\Validator\Mapping\ClassMetadata;
use Symfony\Components\Validator\Mapping\Loader\LoaderChain;
use Symfony\Components\Validator\Mapping\Loader\StaticMethodLoader;
use Symfony\Components\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Components\Validator\MessageInterpolator\XliffMessageInterpolator;
use Symfony\Foundation\UniversalClassLoader;
// path pointing to Symfony 2
define('SYMFONY_DIR', __DIR__);
require_once SYMFONY_DIR.'/src/Symfony/Foundation/UniversalClassLoader.php';
class User
{
public $firstName;
public $lastName;
public $email;
public $married = false;
public $age = 3;
public $gender = 'male';
public $address;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('firstName', new MinLength(2));
$metadata->addPropertyConstraint('lastName', new MinLength(2));
$metadata->addPropertyConstraint('email', new Email());
$metadata->addPropertyConstraint('age', new Min(6));
$metadata->addPropertyConstraint('gender', new Choice(array('choices' => self::getGenders())));
$metadata->addPropertyConstraint('address', new Valid());
}
public static function getGenders()
{
return array('male', 'female');
}
}
class Address
{
public $street;
public $zipCode;
public $city;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('street', new MinLength(6));
$metadata->addPropertyConstraint('zipCode', new MinLength(4));
$metadata->addPropertyConstraint('zipCode', new MaxLength(5));
$metadata->addPropertyConstraint('zipCode', new AssertType('numeric'));
$metadata->addPropertyConstraint('city', new MinLength(3));
}
}
class UserAdmin
{
public $user;
public $hasAddress;
public $address;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('user', new Valid());
$metadata->addPropertyConstraint('address', new Valid());
}
public function __construct(User $user)
{
$this->user = $user;
$this->hasAddress = ($this->user->address !== null);
$this->address = $this->hasAddress ? $this->user->address : new Address();
}
public function persist()
{
$this->user->address = $this->hasAddress ? $this->address : null;
// etc
var_dump($this->user);
}
}
\Locale::setDefault('de_AT');
// initialize autoloader
$loader = new UniversalClassLoader();
$loader->registerNamespace('Symfony', SYMFONY_DIR.'/src');
$loader->register();
// initialize validator
$metadataFactory = new ClassMetadataFactory(new LoaderChain(array(
new StaticMethodLoader('loadValidatorMetadata'),
new XmlFileLoader(SYMFONY_DIR.'/src/Symfony/Components/Form/Resources/config/validation.xml'),
)));
$validatorFactory = new ConstraintValidatorFactory();
$messageInterpolator = new XliffMessageInterpolator(SYMFONY_DIR.'/src/Symfony/Components/Validator/Resources/i18n/messages.en.xml');
$validator = new Validator($metadataFactory, $validatorFactory, $messageInterpolator);
// Note: the above code will be taken care of by the Symfony 2 core
// create new business object
$user = new User();
$userAdmin = new UserAdmin($user);
// create form to interact with the business object
$form = new Form('admin', $userAdmin, $validator);
$userGroup = new FieldGroup('user');
$userGroup->add(new TextField('firstName'));
$userGroup->add(new TextField('lastName'));
$userGroup->add(new DoubleTextField('email'));
$userGroup->add(new CheckboxField('married'));
$userGroup->add(new NumberField('age'));
$userGroup->add(new ChoiceField('gender', array('choices' => array_combine(User::getGenders(), User::getGenders()))));
$addressGroup = new FieldGroup('address');
$addressGroup->add(new TextField('street'));
$addressGroup->add(new TextField('zipCode'));
$addressGroup->add(new TextField('city'));
$form->add($userGroup);
$form->add(new CheckboxField('hasAddress'));
$form->add($addressGroup);
// bind POST data to the form
if (isset($_POST['admin']))
{
$form->bind($_POST['admin']);
if ($form->isValid())
{
$userAdmin->persist();
}
}
?>
<form action="#" method="post">
<?php echo $form->renderErrors() ?>
<?php echo $form->render() ?>
<input type="submit" value="Submit" />
</form>
@ornicar
Copy link

ornicar commented May 26, 2010

nice, thanks a lot for this gist.
I'm using the forms in a simple project, and I noticed HTML labels are not handled. Also, when using a Collection Field with an array of Doctrine entities, the original object is updated with an ID, instead of a record.
For example I have a Project form, containing a Collection Field of Stories. $project->setStory() is called with a story ID as parameter, instead of a Story entity.
And if the project already has a story, I suppose the SelectRenderer gets a bad default value as I get

Catchable Fatal Error: Object of class Doctrine\ORM\Persisters\BasicEntityPersister could not be converted to string in /home/thib/data/workspace/miam/src/vendor/Symfony/src/Symfony/Components/Form/Renderer/SelectRenderer.php line 48

How do you plan to handle that? Does it belong to the DoctrineFormBundle?

@webmozart
Copy link
Author

Labels are not handled by intention. They belong to the templates. Designers should be able to modify them. Therefore the new form framework refuses to store labels alltogether. The $form->render() method's sole purpose is to generate rapid, working draft.

As for the choice field, I haven't tested it with a collection of objects yet. What do you think about the two additional field options "key_field" and "value_field" which allows to define a key and value property when the collection is filled with objects/arrays?

Example:

$field = new ChoiceField('story', array(
  'key_field' => 'id', // results in either ->getId() or ->id, depending on which is publicly available
  'value_field' => 'title',
  'choices' => $objects,
);

@ornicar
Copy link

ornicar commented May 26, 2010

I agree with your approach of labels.

About entity collections, the key and value fields are not really the problem as we always can do it manually.

But when the SelectRenderer is rendered, the default value is an entity, and it produces a catchable fatal error.
Also, the Project population triggered by ProjectForm tries to set a story id instead of a story entity, and it fails too.

@webmozart
Copy link
Author

Yes. It seems to be necessary to override getDisplayedData() in ChoiceField to achieve this, but I'll look into that more closely.

As for your implementation: I don't recommend to usually override the Form's constructor. You have the configure() method at your disposal.
The form name and validator should usually be injected from outside. The form name, because it belongs to the action (which must know the name, too, to get data from the POST request). The validator, because it should usually be shared globally in an application by all processes that require validation.

@ornicar
Copy link

ornicar commented May 26, 2010

Ok, I'll move the code out of the constructor, thx.
About the name and validator injection, I agree. But I suppose their will be a Bundle to integrate Forms with Symfony, providing an easy way to inject complex dependencies such as the validator.
Waiting for it, I didn't want to clutter my controller with the validator creation, so I moved it temporarly to the Form.

Thanks for your suggestions. We'll continue working on this project, so have a look at it if you are curious about how people use your work.

@webmozart
Copy link
Author

The validator will most probably be created by the DIC and injected into the controller. When creating a form, you can thus simply refer to the controller property.

I will have a look at your project. Let me know if you find any more limitations or bugs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment