La implementación actual de ProductForm
sufre una carencia importante. Como
los campos filename
y caption
son obligatorios en ProductPhotoForm
, la
validación del formulario principal siempre falla a menos que el usuario suba
dos nuevas fotos. En otras palabras, el usuario no puede modificar solamente
el precio de Product
sin tener que subir dos nuevas fotos del producto.
Por este motivo se van a redefinir los requisitos de la aplicación para que si
el usuario deja vacíos todos los campos de tipo ProductPhotoForm
, el formulario
principal los ignore. No obstante, si al menos un campo de este tipo tiene
información (sea el caption
o el filename
), el formulario realiza la
validación tradicional y se guarda normalmente. Para conseguirlo, se va a
utilizar una técnica avanzada que hace uso de un post-validador propio.
El primer paso consiste en modificar el formulario ProductPhotoForm
para hacer
que los campos caption
y filename
sean opcionales:
// lib/form/doctrine/ProductPhotoForm.class.php
public function configure()
{
$this->setValidator('filename', new sfValidatorFile(array(
'mime_types' => 'web_images',
'path' => sfConfig::get('sf_upload_dir').'/products',
'required' => false,
)));
$this->validatorSchema['caption']->setOption('required', false);
}
El código anterior establece la opción required
a false
al redefinir el
validador por defecto del campo filename
. Además, también se ha establecido
de forma explícita la opción required
del campo caption
a false
.
Seguidamente se añade el post-validador en el formulario ProductPhotoCollectionForm
:
// lib/form/doctrine/ProductPhotoCollectionForm.class.php
public function configure()
{
// ...
$this->mergePostValidator(new ProductPhotoValidatorSchema());
}
Un post-validador es un tipo especial de validador que tiene acceso a todos los
valores enviados, a diferencia de un validador que sólo pude acceder a un único
campo del formulario. Uno de los post-validadores más utilizados es
sfValidatorSchemaCompare
que comprueba por ejemplo que el valor de un campo
sea inferior al del otro campo.
6.9.1. Creando un validador propio
Afortunadamente es muy sencillo crear un validador propio. Crea un nuevo archivo
llamado ProductPhotoValidatorSchema.class.php
y guárdalo en el directorio
lib/validator/
(debes crear este directorio a mano):
// lib/validator/ProductPhotoValidatorSchema.class.php
class ProductPhotoValidatorSchema extends sfValidatorSchema
{
protected function configure($options = array(), $messages = array())
{
$this->addMessage('caption', 'The caption is required.');
$this->addMessage('filename', 'The filename is required.');
}
protected function doClean($values)
{
$errorSchema = new sfValidatorErrorSchema($this);
foreach($values as $key => $value)
{
$errorSchemaLocal = new sfValidatorErrorSchema($this);
// se ha rellenado el campo filename pero no el campo caption
if ($value['filename'] && !$value['caption'])
{
$errorSchemaLocal->addError(new sfValidatorError($this, 'required'), 'caption');
}
// se ha rellenado el campo caption pero no el campo filename
if ($value['caption'] && !$value['filename'])
{
$errorSchemaLocal->addError(new sfValidatorError($this, 'required'), 'filename');
}
// no se ha rellenado ni caption ni filename, se eliminan los valores vacíos
if (!$value['filename'] && !$value['caption'])
{
unset($values[$key]);
}
// algun error para este formulario embebido
if (count($errorSchemaLocal))
{
$errorSchema->addError($errorSchemaLocal, (string) $key);
}
}
// lanza un error para el formulario principal
if (count($errorSchema))
{
throw new sfValidatorErrorSchema($this, $errorSchema);
}
return $values;
}
}
Nota Todos los validadores heredan de sfValidatorBase
y solamente requieren el
método doClean()
. También se puede emplear el método configure()
para
añadir opciones o mensajes al validador. En este caso, se han añadido dos
mensajes al validador. Igualmente, se pueden añadir opciones adicionales
mediante el método addOption()
.
El método doClean()
se encarga de limpiar y validar los datos enviados. La
propia lógica del validador es muy simple:
- Si la foto enviada solamente tiene un nombre de archivo o un título, se lanza
un error (
sfValidatorErrorSchema
) con el mensaje apropiado - Si la foto enviada no tiene ni nombre de archivo ni título, se eliminan los dos valores para evitar guardar una foto vacía
- Si no se producen errores de validación, el método devuelve un array con los datos limpios
Nota Como en este caso el validador propio se va a utilizar como post-validador,
su método doClean()
espera un array con los valores enviados por el usuario
y devuelve un array con los valores limpios. Los validadores propios para
campos individuales también se pueden crear igual de fácil. En este caso, el
método doClean()
solamente espera un valor (el valor enviado por el usuario)
y devuelve un único valor.
El último paso consiste en redefinir el método saveEmbeddedForms()
de ProductForm
para eliminar los formularios de fotos vacíos de forma que no se guarde una
foto vacía en la base de datos (ya que se lanzaría una excepción porque la
columna caption
es obligatoria):
public function saveEmbeddedForms($con = null, $forms = null)
{
if (null === $forms)
{
$photos = $this->getValue('newPhotos');
$forms = $this->embeddedForms;
foreach ($this->embeddedForms['newPhotos'] as $name => $form)
{
if (!isset($photos[$name]))
{
unset($forms['newPhotos'][$name]);
}
}
}
return parent::saveEmbeddedForms($con, $forms);
}