Development

/branches/1.2/lib/widget/sfWidgetFormSchema.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/widget/sfWidgetFormSchema.class.php

Revision 21875, 21.9 kB (checked in by fabien, 5 years ago)

[1.2, 1.3] fixed PHPDoc (closes #6279)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfWidgetFormSchema represents an array of fields.
13  *
14  * A field is a named validator.
15  *
16  * @package    symfony
17  * @subpackage widget
18  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19  * @version    SVN: $Id$
20  */
21 class sfWidgetFormSchema extends sfWidgetForm implements ArrayAccess
22 {
23   const
24     FIRST  = 'first',
25     LAST   = 'last',
26     BEFORE = 'before',
27     AFTER  = 'after';
28
29   protected static
30     $defaultFormatterName = 'table';
31
32   protected
33     $parent         = null,
34     $formFormatters = array(),
35     $options        = array(),
36     $fields         = array(),
37     $positions      = array(),
38     $helps          = array();
39
40   /**
41    * Constructor.
42    *
43    * The first argument can be:
44    *
45    *  * null
46    *  * an array of sfWidget instances
47    *
48    * Available options:
49    *
50    *  * name_format:    The sprintf pattern to use for input names
51    *  * form_formatter: The form formatter name (table and list are bundled)
52    *
53    * @param mixed $fields     Initial fields
54    * @param array $options    An array of options
55    * @param array $attributes An array of default HTML attributes
56    * @param array $labels     An array of HTML labels
57    * @param array $helps      An array of help texts
58    *
59    * @throws InvalidArgumentException when the passed fields not null or array
60    *
61    * @see sfWidgetForm
62    */
63   public function __construct($fields = null, $options = array(), $attributes = array(), $labels = array(), $helps = array())
64   {
65     $this->addOption('name_format', '%s');
66     $this->addOption('form_formatter', null);
67
68     parent::__construct($options, $attributes);
69
70     if (is_array($fields))
71     {
72       foreach ($fields as $name => $widget)
73       {
74         $this[$name] = $widget;
75       }
76     }
77     else if (!is_null($fields))
78     {
79       throw new InvalidArgumentException('sfWidgetFormSchema constructor takes an array of sfWidget objects.');
80     }
81
82     $this->setLabels($labels);
83     $this->helps = $helps;
84   }
85
86   /**
87    * Sets the default value for a field.
88    *
89    * @param string $name  The field name
90    * @param string $value The default value (required - the default value is here because PHP do not allow signature changes with inheritance)
91    */
92   public function setDefault($name, $value = null)
93   {
94     $this[$name]->setDefault($value);
95   }
96
97   /**
98    * Gets the default value of a field.
99    *
100    * @param string $name The field name (required - the default value is here because PHP do not allow signature changes with inheritance)
101    *
102    * @return string The default value
103    */
104   public function getDefault($name = null)
105   {
106     return $this[$name]->getDefault();
107   }
108
109   /**
110    * Sets the default values for the widget.
111    *
112    * @param array $values The default values for the widget
113    */
114   public function setDefaults(array $values)
115   {
116     foreach ($this->fields as $name => $widget)
117     {
118       if (array_key_exists($name, $values))
119       {
120         $widget->setDefault($values[$name]);
121       }
122     }
123   }
124
125   /**
126    * Returns the defaults values for the widget schema.
127    *
128    * @return array An array of default values
129    */
130   public function getDefaults()
131   {
132     $defaults = array();
133
134     foreach ($this->fields as $name => $widget)
135     {
136       $defaults[$name] = $widget instanceof sfWidgetFormSchema ? $widget->getDefaults() : $widget->getDefault();
137     }
138
139     return $defaults;
140   }
141
142   /**
143    * Adds a form formatter.
144    *
145    * @param string                      $name      The formatter name
146    * @param sfWidgetFormSchemaFormatter $formatter An sfWidgetFormSchemaFormatter instance
147    */
148   public function addFormFormatter($name, sfWidgetFormSchemaFormatter $formatter)
149   {
150     $this->formFormatters[$name] = $formatter;
151   }
152
153   /**
154    * Returns all the form formats defined for this form schema.
155    *
156    * @return array An array of named form formats
157    */
158   public function getFormFormatters()
159   {
160     return $this->formFormatters;
161   }
162
163   /**
164    * Sets the generic default formatter name used by the class. If you want all
165    * of your forms to be generated with the <code>list</code> format, you can
166    * do it in a project or application configuration class:
167    *
168    * <pre>
169    * class ProjectConfiguration extends sfProjectConfiguration
170    * {
171    *   public function setup()
172    *   {
173    *     sfWidgetFormSchema::setDefaultFormFormatterName('list');
174    *   }
175    * }
176    * </pre>
177    *
178    * @param string $name New default formatter name
179    */
180   static public function setDefaultFormFormatterName($name)
181   {
182     self::$defaultFormatterName = $name;
183   }
184
185   /**
186    * Sets the form formatter name to use when rendering the widget schema.
187    *
188    * @param string $name The form formatter name
189    */
190   public function setFormFormatterName($name)
191   {
192     $this->options['form_formatter'] = $name;
193   }
194
195   /**
196    * Gets the form formatter name that will be used to render the widget schema.
197    *
198    * @return string The form formatter name
199    */
200   public function getFormFormatterName()
201   {
202     return is_null($this->options['form_formatter']) ? self::$defaultFormatterName : $this->options['form_formatter'];
203   }
204
205   /**
206    * Returns the form formatter to use for widget schema rendering
207    *
208    * @return sfWidgetFormSchemaFormatter sfWidgetFormSchemaFormatter instance
209    *
210    * @throws InvalidArgumentException when the form formatter not exists
211    */
212   public function getFormFormatter()
213   {
214     $name = $this->getFormFormatterName();
215
216     if (!isset($this->formFormatters[$name]))
217     {
218       $class = 'sfWidgetFormSchemaFormatter'.ucfirst($name);
219
220       if (!class_exists($class))
221       {
222         throw new InvalidArgumentException(sprintf('The form formatter "%s" does not exist.', $name));
223       }
224
225       $this->formFormatters[$name] = new $class($this);
226     }
227
228     return $this->formFormatters[$name];
229   }
230
231   /**
232    * Sets the format string for the name HTML attribute.
233    *
234    * If you are using the form framework with symfony, do not use a reserved word in the
235    * name format.  If you do, symfony may act in an unexpected manner.
236    *
237    * For symfony 1.1 and 1.2, the following words are reserved and must NOT be used as
238    * the name format:
239    *
240    *  * module    (example: module[%s])
241    *  * action    (example: action[%s])
242    *
243    * However, you CAN use other variations, such as actions[%s] (note the s).
244    *
245    * @param string $format The format string (must contain a %s for the name placeholder)
246    *
247    * @throws InvalidArgumentException when no %s exists in the format
248    */
249   public function setNameFormat($format)
250   {
251     if (false !== $format && false === strpos($format, '%s'))
252     {
253       throw new InvalidArgumentException(sprintf('The name format must contain %%s ("%s" given)', $format));
254     }
255
256     $this->options['name_format'] = $format;
257   }
258
259   /**
260    * Gets the format string for the name HTML attribute.
261    *
262    * @return string The format string
263    */
264   public function getNameFormat()
265   {
266     return $this->options['name_format'];
267   }
268
269   /**
270    * Sets the label names to render for each field.
271    *
272    * @param array $labels  An array of label names
273    */
274   public function setLabels(array $labels)
275   {
276     foreach ($this->fields as $name => $widget)
277     {
278       if (array_key_exists($name, $labels))
279       {
280         $widget->setLabel($labels[$name]);
281       }
282     }
283   }
284
285   /**
286    * Gets the labels.
287    *
288    * @return array An array of label names
289    */
290   public function getLabels()
291   {
292     $labels = array();
293
294     foreach ($this->fields as $name => $widget)
295     {
296       $labels[$name] = $widget->getLabel();
297     }
298
299     return $labels;
300   }
301
302   /**
303    * Sets a label.
304    *
305    * @param string $name  The field name
306    * @param string $value The label name (required - the default value is here because PHP do not allow signature changes with inheritance)
307    *
308    * @throws InvalidArgumentException when you try to set a label on a none existing widget
309    */
310   public function setLabel($name, $value = null)
311   {
312     if (2 == func_num_args())
313     {
314       if (!isset($this->fields[$name]))
315       {
316         throw new InvalidArgumentException(sprintf('Unable to set the label on an unexistant widget ("%s").', $name));
317       }
318
319       $this->fields[$name]->setLabel($value);
320     }
321     else
322     {
323       // set the label for this widget schema
324       parent::setLabel($name);
325     }
326   }
327
328   /**
329    * Gets a label by field name.
330    *
331    * @param  string $name  The field name (required - the default value is here because PHP do not allow signature changes with inheritance)
332    *
333    * @return string The label name or an empty string if it is not defined
334    *
335    * @throws InvalidArgumentException when you try to get a label for a none existing widget
336    */
337   public function getLabel($name = null)
338   {
339     if (1 == func_num_args())
340     {
341       if (!isset($this->fields[$name]))
342       {
343         throw new InvalidArgumentException(sprintf('Unable to get the label on an unexistant widget ("%s").', $name));
344       }
345
346       return $this->fields[$name]->getLabel();
347     }
348     else
349     {
350       // label for this widget schema
351       return parent::getLabel();
352     }
353   }
354
355   /**
356    * Sets the help texts to render for each field.
357    *
358    * @param array $helps An array of help texts
359    */
360   public function setHelps(array $helps)
361   {
362     $this->helps = $helps;
363   }
364
365   /**
366    * Sets the help texts.
367    *
368    * @return array An array of help texts
369    */
370   public function getHelps()
371   {
372     return $this->helps;
373   }
374
375   /**
376    * Sets a help text.
377    *
378    * @param string $name The field name
379    * @param string $help The help text
380    */
381   public function setHelp($name, $help)
382   {
383     $this->helps[$name] = $help;
384   }
385
386   /**
387    * Gets a text help by field name.
388    *
389    * @param string $name The field name
390    *
391    * @return string The help text or an empty string if it is not defined
392    */
393   public function getHelp($name)
394   {
395     return array_key_exists($name, $this->helps) ? $this->helps[$name] : '';
396   }
397
398   /**
399    * Gets the stylesheet paths associated with the widget.
400    *
401    * @return array An array of stylesheet paths
402    */
403   public function getStylesheets()
404   {
405     $stylesheets = array();
406
407     foreach ($this->fields as $field)
408     {
409       $stylesheets = array_merge($stylesheets, $field->getStylesheets());
410     }
411
412     return $stylesheets;
413   }
414
415   /**
416    * Gets the JavaScript paths associated with the widget.
417    *
418    * @return array An array of JavaScript paths
419    */
420   public function getJavaScripts()
421   {
422     $javascripts = array();
423
424     foreach ($this->fields as $field)
425     {
426       $javascripts = array_merge($javascripts, $field->getJavaScripts());
427     }
428
429     return array_unique($javascripts);
430   }
431
432   /**
433    * Returns true if the widget schema needs a multipart form.
434    *
435    * @return bool true if the widget schema needs a multipart form, false otherwise
436    */
437   public function needsMultipartForm()
438   {
439     foreach ($this->fields as $field)
440     {
441       if ($field->needsMultipartForm())
442       {
443         return true;
444       }
445     }
446
447     return false;
448   }
449
450   /**
451    * Renders a field by name.
452    *
453    * @param string $name       The field name
454    * @param string $value      The field value
455    * @param array  $attributes An array of HTML attributes to be merged with the current HTML attributes
456    * @param array  $errors     An array of errors for the field
457    *
458    * @return string An HTML string representing the rendered widget
459    *
460    * @throws InvalidArgumentException when the widget not exist
461    */
462   public function renderField($name, $value = null, $attributes = array(), $errors = array())
463   {
464     if (is_null($widget = $this[$name]))
465     {
466       throw new InvalidArgumentException(sprintf('The field named "%s" does not exist.', $name));
467     }
468
469     if ($widget instanceof sfWidgetFormSchema && $errors && !$errors instanceof sfValidatorErrorSchema)
470     {
471       $errors = new sfValidatorErrorSchema($errors->getValidator(), array($errors));
472     }
473
474     // we clone the widget because we want to change the id format temporarily
475     $clone = clone $widget;
476     $clone->setIdFormat($this->options['id_format']);
477
478     return $clone->render($this->generateName($name), $value, array_merge($clone->getAttributes(), $attributes), $errors);
479   }
480
481   /**
482    * Renders the widget.
483    *
484    * @param string $name       The name of the HTML widget
485    * @param mixed  $values     The values of the widget
486    * @param array  $attributes An array of HTML attributes
487    * @param array  $errors     An array of errors
488    *
489    * @return string An HTML representation of the widget
490    *
491    * @throws InvalidArgumentException when values type is not array|ArrayAccess
492    */
493   public function render($name, $values = array(), $attributes = array(), $errors = array())
494   {
495     if (is_null($values))
496     {
497       $values = array();
498     }
499
500     if (!is_array($values) && !$values instanceof ArrayAccess)
501     {
502       throw new InvalidArgumentException('You must pass an array of values to render a widget schema');
503     }
504
505     $formFormat = $this->getFormFormatter();
506
507     $rows = array();
508     $hiddenRows = array();
509     $errorRows = array();
510
511     // render each field
512     foreach ($this->positions as $name)
513     {
514       $widget = $this[$name];
515       $value = isset($values[$name]) ? $values[$name] : null;
516       $error = isset($errors[$name]) ? $errors[$name] : array();
517       $widgetAttributes = isset($attributes[$name]) ? $attributes[$name] : array();
518
519       if ($widget instanceof sfWidgetForm && $widget->isHidden())
520       {
521         $hiddenRows[] = $this->renderField($name, $value, $widgetAttributes);
522       }
523       else
524       {
525         $field = $this->renderField($name, $value, $widgetAttributes, $error);
526
527         // don't add a label tag and errors if we embed a form schema
528         $label = $widget instanceof sfWidgetFormSchema ? $this->getFormFormatter()->generateLabelName($name) : $this->getFormFormatter()->generateLabel($name);
529         $error = $widget instanceof sfWidgetFormSchema ? array() : $error;
530
531         $rows[] = $formFormat->formatRow($label, $field, $error, $this->getHelp($name));
532       }
533     }
534
535     if ($rows)
536     {
537       // insert hidden fields in the last row
538       for ($i = 0, $max = count($rows); $i < $max; $i++)
539       {
540         $rows[$i] = strtr($rows[$i], array('%hidden_fields%' => $i == $max - 1 ? implode("\n", $hiddenRows) : ''));
541       }
542     }
543     else
544     {
545       // only hidden fields
546       $rows[0] = implode("\n", $hiddenRows);
547     }
548
549     return $this->getFormFormatter()->formatErrorRow($this->getGlobalErrors($errors)).implode('', $rows);
550   }
551
552   /**
553    * Gets errors that need to be included in global errors.
554    *
555    * @param array $errors An array of errors
556    *
557    * @return string An HTML representation of global errors for the widget
558    */
559   public function getGlobalErrors($errors)
560   {
561     $globalErrors = array();
562
563     // global errors and errors for non existent fields
564     if (!is_null($errors))
565     {
566       foreach ($errors as $name => $error)
567       {
568         if (!isset($this->fields[$name]))
569         {
570           $globalErrors[] = $error;
571         }
572       }
573     }
574
575     // errors for hidden fields
576     foreach ($this->positions as $name)
577     {
578       if ($this[$name] instanceof sfWidgetForm && $this[$name]->isHidden())
579       {
580         if (isset($errors[$name]))
581         {
582           $globalErrors[$this->getFormFormatter()->generateLabelName($name)] = $errors[$name];
583         }
584       }
585     }
586
587     return $globalErrors;
588   }
589
590   /**
591    * Generates a name.
592    *
593    * @param string $name The name
594    *
595    * @return string The generated name
596    */
597   public function generateName($name)
598   {
599     $format = $this->getNameFormat();
600
601     if ('[%s]' == substr($format, -4) && preg_match('/^(.+?)\[(.+)\]$/', $name, $match))
602     {
603       $name = sprintf('%s[%s][%s]', substr($format, 0, -4), $match[1], $match[2]);
604     }
605     else if (false !== $format)
606     {
607       $name = sprintf($format, $name);
608     }
609
610     if ($parent = $this->getParent())
611     {
612       $name = $parent->generateName($name);
613     }
614
615     return $name;
616   }
617
618   /**
619    * Gets the parent widget schema.
620    *
621    * @return sfWidgetFormSchema The parent widget schema
622    */
623   public function getParent()
624   {
625     return $this->parent;
626   }
627
628   /**
629    * Sets the parent widget schema.
630    *
631    * @params sfWidgetFormSchema $parent  The parent widget schema
632    */
633   public function setParent(sfWidgetFormSchema $parent = null)
634   {
635     $this->parent = $parent;
636   }
637
638   /**
639    * Returns true if the schema has a field with the given name (implements the ArrayAccess interface).
640    *
641    * @param string $name The field name
642    *
643    * @return bool true if the schema has a field with the given name, false otherwise
644    */
645   public function offsetExists($name)
646   {
647     return isset($this->fields[$name]);
648   }
649
650   /**
651    * Gets the field associated with the given name (implements the ArrayAccess interface).
652    *
653    * @param string $name The field name
654    *
655    * @return sfWidget|null The sfWidget instance associated with the given name, null if it does not exist
656    */
657   public function offsetGet($name)
658   {
659     return isset($this->fields[$name]) ? $this->fields[$name] : null;
660   }
661
662   /**
663    * Sets a field (implements the ArrayAccess interface).
664    *
665    * @param string   $name   The field name
666    * @param sfWidget $widget An sfWidget instance
667    *
668    * @throws InvalidArgumentException when the field is not instance of sfWidget
669    */
670   public function offsetSet($name, $widget)
671   {
672     if (!$widget instanceof sfWidget)
673     {
674       throw new InvalidArgumentException('A field must be an instance of sfWidget.');
675     }
676
677     if (!isset($this->fields[$name]))
678     {
679       $this->positions[] = (string) $name;
680     }
681
682     $this->fields[$name] = clone $widget;
683
684     if ($widget instanceof sfWidgetFormSchema)
685     {
686       $this->fields[$name]->setParent($this);
687       $this->fields[$name]->setNameFormat($name.'[%s]');
688     }
689   }
690
691   /**
692    * Removes a field by name (implements the ArrayAccess interface).
693    *
694    * @param string $name field name
695    */
696   public function offsetUnset($name)
697   {
698     unset($this->fields[$name]);
699     if (false !== $position = array_search((string) $name, $this->positions))
700     {
701       unset($this->positions[$position]);
702
703       $this->positions = array_values($this->positions);
704     }
705   }
706
707   /**
708    * Returns an array of fields.
709    *
710    * @return sfWidget An array of sfWidget instance
711    */
712   public function getFields()
713   {
714     return $this->fields;
715   }
716
717   /**
718    * Gets the positions of the fields.
719    *
720    * The field positions are only used when rendering the schema with ->render().
721    *
722    * @return array An ordered array of field names
723    */
724   public function getPositions()
725   {
726     return $this->positions;
727   }
728
729   /**
730    * Sets the positions of the fields.
731    *
732    * @param array $positions An ordered array of field names
733    *
734    * @throws InvalidArgumentException when not all fields set in $positions
735    *
736    * @see getPositions()
737    */
738   public function setPositions(array $positions)
739   {
740     $positions = array_values($positions);
741     if (array_diff($positions, array_keys($this->fields)) || array_diff(array_keys($this->fields), $positions))
742     {
743       throw new InvalidArgumentException('Positions must contains all field names.');
744     }
745     foreach ($positions as &$position)
746     {
747       $position = (string) $position;
748     }
749
750     $this->positions = $positions;
751   }
752
753   /**
754    * Moves a field in a given position
755    *
756    * Available actions are:
757    *
758    *  * sfWidgetFormSchema::BEFORE
759    *  * sfWidgetFormSchema::AFTER
760    *  * sfWidgetFormSchema::LAST
761    *  * sfWidgetFormSchema::FIRST
762    *
763    * @param string   $field  The field name to move
764    * @param constant $action The action (see above for all possible actions)
765    * @param string   $pivot  The field name used for AFTER and BEFORE actions
766    *
767    * @throws InvalidArgumentException when field not exist
768    * @throws InvalidArgumentException when relative field not exist
769    * @throws LogicException           when you try to move a field without a relative field
770    * @throws LogicException           when the $action not exist
771    */
772   public function moveField($field, $action, $pivot = null)
773   {
774     $field = (string) $field;
775     if (false === $fieldPosition = array_search($field, $this->positions))
776     {
777       throw new InvalidArgumentException(sprintf('Field "%s" does not exist.', $field));
778     }
779     unset($this->positions[$fieldPosition]);
780     $this->positions = array_values($this->positions);
781
782     if (!is_null($pivot))
783     {
784       $pivot = (string) $pivot;
785       if (false === $pivotPosition = array_search($pivot, $this->positions))
786       {
787         throw new InvalidArgumentException(sprintf('Field "%s" does not exist.', $pivot));
788       }
789     }
790
791     switch ($action)
792     {
793       case sfWidgetFormSchema::FIRST:
794         array_unshift($this->positions, $field);
795         break;
796       case sfWidgetFormSchema::LAST:
797         array_push($this->positions, $field);
798         break;
799       case sfWidgetFormSchema::BEFORE:
800         if (is_null($pivot))
801         {
802           throw new LogicException(sprintf('Unable to move field "%s" without a relative field.', $field));
803         }
804         $this->positions = array_merge(
805           array_slice($this->positions, 0, $pivotPosition),
806           array($field),
807           array_slice($this->positions, $pivotPosition)
808         );
809         break;
810       case sfWidgetFormSchema::AFTER:
811         if (is_null($pivot))
812         {
813           throw new LogicException(sprintf('Unable to move field "%s" without a relative field.', $field));
814         }
815         $this->positions = array_merge(
816           array_slice($this->positions, 0, $pivotPosition + 1),
817           array($field),
818           array_slice($this->positions, $pivotPosition + 1)
819         );
820         break;
821       default:
822         throw new LogicException(sprintf('Unknown move operation for field "%s".', $field));
823     }
824   }
825
826   public function __clone()
827   {
828     foreach ($this->fields as $name => $field)
829     {
830       // offsetSet will clone the field and change the parent
831       $this[$name] = $field;
832     }
833
834     foreach ($this->formFormatters as &$formFormatter)
835     {
836       $formFormatter = clone $formFormatter;
837       $formFormatter->setWidgetSchema($this);
838     }
839   }
840 }
841
Note: See TracBrowser for help on using the browser.