2010-06-24 09:40:05 +01:00
< ? php
namespace Symfony\Components\Form ;
use Symfony\Components\Form\Exception\InvalidPropertyException ;
use Symfony\Components\Form\Exception\PropertyAccessDeniedException ;
use Symfony\Components\Form\Exception\NotBoundException ;
use Symfony\Components\Form\Exception\NotValidException ;
use Symfony\Components\Form\Exception\InvalidConfigurationException ;
use Symfony\Components\Form\ValueTransformer\ValueTransformerInterface ;
use Symfony\Components\Form\ValueTransformer\TransformationFailedException ;
use Symfony\Components\I18N\TranslatorInterface ;
abstract class Field extends Configurable implements FieldInterface
{
2010-06-24 10:24:08 +01:00
/**
* The object used for generating HTML code
* @ var HtmlGeneratorInterface
*/
protected $generator = null ;
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
protected $taintedData = null ;
protected $locale = null ;
protected $translator = null ;
private $errors = array ();
private $key = '' ;
private $parent = null ;
private $renderer = null ;
private $bound = false ;
private $required = null ;
private $data = null ;
private $transformedData = null ;
private $valueTransformer = null ;
private $propertyPath = null ;
public function __construct ( $key , array $options = array ())
{
$this -> addOption ( 'trim' , true );
$this -> addOption ( 'required' , true );
$this -> addOption ( 'disabled' , false );
$this -> addOption ( 'property_path' , ( string ) $key );
$this -> key = ( string ) $key ;
$this -> generator = new HtmlGenerator ();
2010-07-04 15:20:10 +01:00
if ( $this -> locale === null ) {
$this -> locale = class_exists ( '\Locale' , false ) ? \Locale :: getDefault () : 'en' ;
}
2010-06-24 10:24:08 +01:00
parent :: __construct ( $options );
$this -> transformedData = $this -> transform ( $this -> data );
$this -> required = $this -> getOption ( 'required' );
$this -> setPropertyPath ( $this -> getOption ( 'property_path' ));
}
/**
* Clones this field .
*/
public function __clone ()
{
// TODO
}
/**
* Returns the data of the field as it is displayed to the user .
*
* @ return string | array When the field is not bound , the transformed
* default data is returned . When the field is bound ,
* the bound data is returned .
*/
public function getDisplayedData ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return $this -> getTransformedData ();
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Returns the data transformed by the value transformer
*
* @ return string
*/
protected function getTransformedData ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return $this -> transformedData ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function setPropertyPath ( $propertyPath )
2010-06-24 09:40:05 +01:00
{
2010-07-04 16:03:03 +01:00
$this -> propertyPath = $propertyPath === null || $propertyPath === '' ? null : new PropertyPath ( $propertyPath );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function getPropertyPath ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return $this -> propertyPath ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function setKey ( $key )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> key = ( string ) $key ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function getKey ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return $this -> key ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function getName ()
{
return is_null ( $this -> parent ) ? $this -> key : $this -> parent -> getName () . '[' . $this -> key . ']' ;
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function getId ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return is_null ( $this -> parent ) ? $this -> key : $this -> parent -> getId () . '_' . $this -> key ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function setRequired ( $required )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> required = $required ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function isRequired ()
{
if ( is_null ( $this -> parent ) || $this -> parent -> isRequired ()) {
return $this -> required ;
} else {
return false ;
}
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function isDisabled ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
if ( is_null ( $this -> parent ) || ! $this -> parent -> isDisabled ()) {
return $this -> getOption ( 'disabled' );
} else {
return true ;
}
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function setGenerator ( HtmlGeneratorInterface $generator )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> generator = $generator ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function isMultipart ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return false ;
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Returns true if the widget is hidden .
*
* @ return Boolean true if the widget is hidden , false otherwise
*/
public function isHidden ()
{
return false ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function setParent ( FieldInterface $parent = null )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> parent = $parent ;
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Returns the parent field .
*
* @ return FieldInterface The parent field
*/
public function getParent ()
{
return $this -> parent ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Updates the field with default data
*
* @ see FieldInterface
*/
public function setData ( $data )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> data = $data ;
$this -> transformedData = $this -> transform ( $data );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Binds POST data to the field , transforms and validates it .
*
* @ param string | array $taintedData The POST data
* @ return boolean Whether the form is valid
* @ throws AlreadyBoundException when the field is already bound
*/
public function bind ( $taintedData )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> transformedData = is_array ( $taintedData ) || is_object ( $taintedData ) ? $taintedData : ( string ) $taintedData ;
$this -> bound = true ;
$this -> errors = array ();
if ( is_string ( $this -> transformedData ) && $this -> getOption ( 'trim' )) {
$this -> transformedData = trim ( $this -> transformedData );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
try {
$this -> data = $this -> processData ( $data = $this -> reverseTransform ( $this -> transformedData ));
$this -> transformedData = $this -> transform ( $this -> data );
} catch ( TransformationFailedException $e ) {
// TODO better text
// TESTME
$this -> addError ( 'invalid (localized)' );
}
}
/**
* Processes the bound reverse - transformed data .
*
* This method can be overridden if you want to modify the data entered
* by the user . Note that the data is already in reverse transformed format .
*
* This method will not be called if reverse transformation fails .
*
* @ param mixed $data
* @ return mixed
*/
protected function processData ( $data )
{
return $data ;
}
/**
* Returns the normalized data of the field .
*
* @ return mixed When the field is not bound , the default data is returned .
* When the field is bound , the normalized bound data is
* returned if the field is valid , null otherwise .
*/
public function getData ()
{
return $this -> data ;
}
/**
* Adds an error to the field .
*
* @ see FieldInterface
*/
public function addError ( $message , PropertyPath $path = null , $type = null )
{
$this -> errors [] = $message ;
}
/**
* Returns whether the field is bound .
*
* @ return boolean true if the form is bound to input values , false otherwise
*/
public function isBound ()
{
return $this -> bound ;
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Returns whether the field is valid .
*
* @ return boolean
*/
public function isValid ()
{
return $this -> isBound () ? count ( $this -> errors ) == 0 : false ; // TESTME
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Returns weather there are errors .
*
* @ return boolean true if form is bound and not valid
*/
public function hasErrors ()
{
return $this -> isBound () && ! $this -> isValid ();
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Returns all errors
*
* @ return array An array of errors that occured during binding
*/
public function getErrors ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return $this -> errors ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Sets the locale of this field .
*
* @ see Localizable
*/
public function setLocale ( $locale )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> locale = $locale ;
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
if ( $this -> valueTransformer !== null && $this -> valueTransformer instanceof Localizable ) {
$this -> valueTransformer -> setLocale ( $locale );
}
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Sets the translator of this field .
*
* @ see Translatable
*/
public function setTranslator ( TranslatorInterface $translator )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> translator = $translator ;
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
if ( $this -> valueTransformer !== null && $this -> valueTransformer instanceof Translatable ) {
$this -> valueTransformer -> setTranslator ( $translator );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Translates the text using the associated translator , if available
*
* If no translator is available , the original text is returned without
* modification .
*
* @ param string $text The text to translate
* @ param array $parameters The parameters to insert in the text
* @ return string The translated text
*/
protected function translate ( $text , array $parameters = array ())
{
if ( $this -> translator !== null ) {
$text = $this -> translator -> translate ( $text , $parameters );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
return $text ;
}
/**
* Injects the locale and the translator into the given object , if set .
*
* The locale is injected only if the object implements Localizable . The
* translator is injected only if the object implements Translatable .
*
* @ param object $object
*/
protected function injectLocaleAndTranslator ( $object )
{
if ( $object instanceof Localizable ) {
$object -> setLocale ( $this -> locale );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
if ( ! is_null ( $this -> translator ) && $object instanceof Translatable ) {
$object -> setTranslator ( $this -> translator );
}
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Sets the ValueTransformer .
*
* @ param ValueTransformerInterface $valueTransformer
*/
public function setValueTransformer ( ValueTransformerInterface $valueTransformer )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
$this -> injectLocaleAndTranslator ( $valueTransformer );
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
$this -> valueTransformer = $valueTransformer ;
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Returns the ValueTransformer .
*
* @ return ValueTransformerInterface
*/
public function getValueTransformer ()
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
return $this -> valueTransformer ;
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Transforms the value if a value transformer is set .
*
* @ param mixed $value The value to transform
* @ return string
*/
protected function transform ( $value )
{
if ( $value === null ) {
return '' ;
} else if ( null === $this -> valueTransformer ) {
return $value ;
} else {
return $this -> valueTransformer -> transform ( $value );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Reverse transforms a value if a value transformer is set .
*
* @ param string $value The value to reverse transform
* @ return mixed
*/
protected function reverseTransform ( $value )
{
if ( $value === '' ) {
return null ;
} else if ( null === $this -> valueTransformer ) {
return $value ;
} else {
return $this -> valueTransformer -> reverseTransform ( $value );
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function updateFromObject ( & $objectOrArray )
{
// TODO throw exception if not object or array
if ( $this -> propertyPath !== null ) {
$this -> propertyPath -> rewind ();
$this -> setData ( $this -> readPropertyPath ( $objectOrArray , $this -> propertyPath ));
} else {
// pass object through if the property path is empty
$this -> setData ( $objectOrArray );
}
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function updateObject ( & $objectOrArray )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
// TODO throw exception if not object or array
if ( $this -> propertyPath !== null ) {
$this -> propertyPath -> rewind ();
$this -> updatePropertyPath ( $objectOrArray , $this -> propertyPath );
}
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* Recursively reads the value of the property path in the data
*
* @ param array | object $objectOrArray An object or array
* @ param PropertyPath $propertyPath A property path pointing to a property
* in the object / array .
*/
protected function readPropertyPath ( & $objectOrArray , PropertyPath $propertyPath )
{
if ( is_object ( $objectOrArray )) {
$value = $this -> readProperty ( $objectOrArray , $propertyPath );
}
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
else {
if ( ! array_key_exists ( $propertyPath -> getCurrent (), $objectOrArray )) {
$objectOrArray [ $propertyPath -> getCurrent ()] = array ();
}
$value =& $objectOrArray [ $propertyPath -> getCurrent ()];
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
if ( $propertyPath -> hasNext ()) {
$propertyPath -> next ();
return $this -> readPropertyPath ( $value , $propertyPath );
} else {
return $value ;
}
}
protected function updatePropertyPath ( & $objectOrArray , PropertyPath $propertyPath )
2010-06-24 09:40:05 +01:00
{
2010-06-24 10:24:08 +01:00
if ( $propertyPath -> hasNext ()) {
if ( is_object ( $objectOrArray )) {
$value = $this -> readProperty ( $objectOrArray , $propertyPath );
}
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
else {
if ( ! array_key_exists ( $propertyPath -> getCurrent (), $objectOrArray )) {
$objectOrArray [ $propertyPath -> getCurrent ()] = array ();
}
$value =& $objectOrArray [ $propertyPath -> getCurrent ()];
}
$propertyPath -> next ();
$this -> updatePropertyPath ( $value , $propertyPath );
} else {
$this -> updateProperty ( $objectOrArray , $propertyPath );
}
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
/**
* Reads a specific element of the given data
*
* If the data is an array , the value at index $element is returned .
*
* If the data is an object , either the result of get { $element }(),
* is { $element }() or the property $element is returned . If none of these
* is publicly available , an exception is thrown
*
* @ param object $object The data to read
* @ param string $element The element to read from the data
* @ return mixed The value of the element
*/
protected function readProperty ( $object , PropertyPath $propertyPath )
{
if ( $propertyPath -> isIndex ()) {
if ( ! $object instanceof \ArrayAccess ) {
throw new InvalidPropertyException ( sprintf ( 'Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess' , $propertyPath -> getCurrent (), get_class ( $object )));
}
return $object [ $propertyPath -> getCurrent ()];
} else {
$reflClass = new \ReflectionClass ( $object );
$getter = 'get' . ucfirst ( $propertyPath -> getCurrent ());
$isser = 'is' . ucfirst ( $propertyPath -> getCurrent ());
$property = $propertyPath -> getCurrent ();
if ( $reflClass -> hasMethod ( $getter )) {
if ( ! $reflClass -> getMethod ( $getter ) -> isPublic ()) {
throw new PropertyAccessDeniedException ( sprintf ( 'Method "%s()" is not public in class "%s"' , $getter , $reflClass -> getName ()));
}
return $object -> $getter ();
} else if ( $reflClass -> hasMethod ( $isser )) {
if ( ! $reflClass -> getMethod ( $isser ) -> isPublic ()) {
throw new PropertyAccessDeniedException ( sprintf ( 'Method "%s()" is not public in class "%s"' , $isser , $reflClass -> getName ()));
}
return $object -> $isser ();
} else if ( $reflClass -> hasProperty ( $property )) {
if ( ! $reflClass -> getProperty ( $property ) -> isPublic ()) {
throw new PropertyAccessDeniedException ( sprintf ( 'Property "%s" is not public in class "%s". Maybe you should create the method "get%s()" or "is%s()"?' , $property , $reflClass -> getName (), ucfirst ( $property ), ucfirst ( $property )));
}
return $object -> $property ;
} else {
throw new InvalidPropertyException ( sprintf ( 'Neither property "%s" nor method "%s()" nor method "%s()" exists in class "%s"' , $property , $getter , $isser , $reflClass -> getName ()));
}
}
}
2010-06-24 09:40:05 +01:00
2010-06-24 10:24:08 +01:00
protected function updateProperty ( & $objectOrArray , PropertyPath $propertyPath )
{
if ( is_object ( $objectOrArray ) && $propertyPath -> isIndex ()) {
if ( ! $objectOrArray instanceof \ArrayAccess ) {
throw new InvalidPropertyException ( sprintf ( 'Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess' , $propertyPath -> getCurrent (), get_class ( $objectOrArray )));
}
$objectOrArray [ $propertyPath -> getCurrent ()] = $this -> getData ();
} else if ( is_object ( $objectOrArray )) {
$reflClass = new \ReflectionClass ( $objectOrArray );
$setter = 'set' . ucfirst ( $propertyPath -> getCurrent ());
$property = $propertyPath -> getCurrent ();
if ( $reflClass -> hasMethod ( $setter )) {
if ( ! $reflClass -> getMethod ( $setter ) -> isPublic ()) {
throw new PropertyAccessDeniedException ( sprintf ( 'Method "%s()" is not public in class "%s"' , $setter , $reflClass -> getName ()));
}
$objectOrArray -> $setter ( $this -> getData ());
} else if ( $reflClass -> hasProperty ( $property )) {
if ( ! $reflClass -> getProperty ( $property ) -> isPublic ()) {
throw new PropertyAccessDeniedException ( sprintf ( 'Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?' , $property , $reflClass -> getName (), ucfirst ( $property )));
}
$objectOrArray -> $property = $this -> getData ();
} else {
throw new InvalidPropertyException ( sprintf ( 'Neither element "%s" nor method "%s()" exists in class "%s"' , $property , $setter , $reflClass -> getName ()));
}
} else {
$objectOrArray [ $propertyPath -> getCurrent ()] = $this -> getData ();
}
2010-06-24 09:40:05 +01:00
}
2010-06-24 10:24:08 +01:00
/**
* { @ inheritDoc }
*/
public function renderErrors ()
{
$html = '' ;
if ( $this -> hasErrors ()) {
$html .= " <ul> \n " ;
foreach ( $this -> getErrors () as $error ) {
$html .= " <li> " . $error . " </li> \n " ;
}
$html .= " </ul> \n " ;
}
return $html ;
}
2010-06-24 09:40:05 +01:00
}