Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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