Model binding pro Nette formuláře
- Honza Marek
- Člen | 1664
Poslední dobou jsem ve škole dělal něco v ASP.net MVC 2 a taky si četl dokumentaci Symfony2. A zalíbila se mi věc, která se jmenuje model binding. Znamená to, že data z formuláře se rovnou sypou do nějaké entity. Potom pracuji již s naplněnou entitou.
Sám pro sebe jsem si něco takového implementoval, ale není to dostatečně obecné, aby se to dalo zveřejnit.
Malý příklad:
Formulářový model. V jednoduchých případech např. Doctrine entita, ve složitějších vlastní třída.
class FormModel
{
/** @validation:NotBlank */
public $name;
/**
* @validation:NotBlank
* @validation:Email(message="E-mail rozbitej")
*/
public $email = "@"; // defaultní hodnota
private $date;
public function __construct()
{
$this->date = new DateTime; // defaultní hodnota
}
public function setDate(DateTime $date)
{
$this->date = $date;
}
public function getDate()
{
return $this->date;
}
}
Továrnička formuláře:
protected function createComponentForm()
{
$form = new AppForm;
$form->bindModel(new FormModel);
$form->setValidator($this->getService("Validator")); // možná by to mohl umět sám od sebe
// políčka vyjadřující datové typy - něco takového se do Nette 2.0 chystá, pokud vím
$this->addText("name");
$this->addEmail("email");
$this->addDatePicker("date");
$form->onSubmit[] = function ($form) {
ulozit($form->getModel());
// flashmessage, redirect
};
}
- jtousek
- Člen | 951
Ta služba Validator je třída ze Symfony?
A jinak ano, líbí se mi to. V případě entit Doctrine 2 provázání formuláře přímo s entitou bije do očí a také mám v úmyslu to implementovat. Jen jsem si říkal že počkám na nové Nette formuláře než se do toho pustím.
//EDIT: Ale dát to obecně do frameworku, s tím už bych trochu váhal – ne každý používá Doctrine. Měl jsem spíše v úmyslu udělat to mimo. Pokud to bude přímo ve frameworku, nevadí mi to. (s rozumným API)
Editoval jtousek (26. 11. 2010 15:04)
- romansklenar
- Člen | 655
Takovýto postup jde použít jen u entit, které nemájí žádné asociace. S těmi přicházejí už další komplikace a chtělo by na to myslet, pokud se bude nějaký takový doplněk tvořit.
- Honza Marek
- Člen | 1664
jtousek
Ta služba Validator je třída ze Symfony?
Mohla by být.
jtousek
Ale dát to obecně do frameworku, s tím už bych trochu váhal – ne každý používá Doctrine.
mkoubik
u menších projektů je nejjednodušší prostě vzít pole z $form->values
Může to existovat s klasickým způsobem s getValues vedle sebe. Pokud budu chtít mít výstupem formuláře nějakou chytrou třídu místo pole, použiju binding.
romansklenar
Takovýto postup jde použít jen u entit, které nemájí žádné asociace.
Jsou dva způsoby jak to řešit.
- Vlastní formulářový prvek (CategorySelectBox, …).
- Někde něčemu zaregistrovat, že pro typ BaseEntity má provést
$value->getId()
a podobně.
- srigi
- Nette Blogger | 558
No parada, v Ostrave na PS som toto prezentoval, ze framework Yii to ma uz od zaciatku. Dostalo sa mi chladnej odozvy, ze je to moc previazane a pod. Teraz sa to ako bumerang vracia, plno ludi stale hlada riesenie – Entita, Formular, jednotna validacia. Pridam len malu ukazku toho riesenia v Yii.
Model (AR classa)
<?php
class User extends CActiveRecord
{
/**
* @var integer $id
* @var string $email
* @var string $password
* @var string $name
* @var string $role
* @var integer $company_id
*/
/** @var string */
public passwordRepeat; // for password updates (virtual, not in DB)
/**
* @return Users
*/
public static function model($className=__CLASS__) {
return parent::model($className);
}
public function rules() {
return array(
array('email, name, password, passwordRepeat', 'required', 'on'=>'register'),
array('email, name', 'required', 'on'=>'update'),
array('email, name, password, passwordRepeat', 'required', 'on'=>'changePasswd'),
array('email', 'length', 'max'=>45),
array('email', 'unique'),
array('email', 'email'),
array('password', 'length', 'max'=>40),
array('password', 'compare', 'compareAttribute'=>'passwordRepeat', 'on'=>'register, changePasswd'),
array('passwordRepeat', 'length', 'max'=>40),
array('name', 'length', 'max'=>80),
array('role', 'length', 'max'=>20),
array('role', 'match', 'pattern'=>'/admin|member/', 'on'=>'save'),
array('company_id', 'numerical', 'integerOnly'=>true),
);
}
public function relations() {
return array(
'ratings' => array(self::HAS_MANY, 'Ratings', 'user_id'),
'company' => array(self::BELONGS_TO, 'Companies', 'company_id'),
);
}
public function attributeLabels() {
return array(
'email' => 'Email',
'password' => 'Password',
'name' => 'Name',
);
}
}
?>
Controller (spracovanie formu)
public function actionUpdateuser()
{
if (!isset($_GET['id'])) {
$this->redirect(array('staff'));
}
$model = User::model()->with('company')->findByPk($_GET['id']);
if (!$model || ($model->company_id != Yii::app()->user->companyId)) {
$this->redirect(array('staff'));
}
if (isset($_POST['User'])) {
$_POST['User']['id'] = Yii::app()->user->id; // User cannot change ID via form
$_POST['User']['role'] = Yii::app()->user->role; // User cannot change role via form
$model->attributes = $_POST['Users'];
if (empty($model->password)) {
$model->setScenario('update');
} else {
$model->setScenario('changePasswd');
}
if ($model->validate()) {
if (isset($model->password)) {
$model->password = sha1($model->password.Yii::app()->params['dbsalt']);
}
try {
$model->save(false);
} catch (CDbException $e) {
$this->render('updateuser.error');
return;
}
$this->redirect(array('updateuser', 'id'=>$_GET['id']));
}
}
$model->password = $model->passwordRepeat = ''; // clean password fields
$this->render('updateuser', array('model'=>$model));
}
Sablona
<?= CHtml::beginForm(); ?>
<?= CHtml::errorSummary($model); ?>
<?= CHtml::activeLabel($model, 'name'); ?>
<?= CHtml::activeTextField($model, 'name'); ?>
<?= CHtml::activeLabel($model, 'email'); ?>
<?= CHtml::activeTextField($model, 'email'); ?>
<?= CHtml::activeLabel($model, 'password'); ?>
<?= CHtml::activePasswordField($model, 'password'); ?>
<?= CHtml::activeLabel($model, 'passwordRepeat'); ?>
<?= CHtml::activePasswordField($model, 'passwordRepeat'); ?>
<?= CHTML::submitButton('save'); ?>
<?= echo CHtml::endForm(); ?>
Vsetko je to krasne previazane (Formular, DB Row), ma to jednotne validacne pravidla a co sa mimoriadne paci (a v Nette netrpezlivo ocakavam) je moznost definovat typ formularoveho policka az v sablone, nezavisle na type Entitnej property.
Editoval srigi (26. 11. 2010 21:13)
- Filip Procházka
- Moderator | 4668
srigi: vypadá to že to funguje, byť to definování vypadá vskutku hrozně :)
Editoval HosipLan (27. 11. 2010 10:18)