Development

/branches/1.2/lib/plugins/sfDoctrinePlugin/lib/form/sfFormDoctrine.class.php

You must first sign up to be able to contribute.

root/branches/1.2/lib/plugins/sfDoctrinePlugin/lib/form/sfFormDoctrine.class.php

Revision 25146, 13.2 kB (checked in by Kris.Wallsmith, 4 years ago)

[1.2] backported r24971

Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6  * (c) Jonathan H. Wage <jonwage@gmail.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 /**
13  * @package    symfony
14  * @subpackage doctrine
15  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
16  * @author     Jonathan H. Wage <jonwage@gmail.com>
17  * @version    SVN: $Id: sfFormDoctrine.class.php 7845 2008-03-12 22:36:14Z fabien $
18  */
19
20 /**
21  * sfFormDoctrine is the base class for forms based on Doctrine objects.
22  *
23  * @package    symfony
24  * @subpackage form
25  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
26  * @author     Jonathan H. Wage <jonwage@gmail.com>
27  * @version    SVN: $Id: sfFormDoctrine.class.php 7845 2008-03-12 22:36:14Z fabien $
28  */
29 abstract class sfFormDoctrine extends sfForm
30 {
31   protected
32     $isNew  = true,
33     $object = null;
34
35   /**
36    * Constructor.
37    *
38    * @param BaseObject A Doctrine object used to initialize default values
39    * @param array      An array of options
40    * @param string     A CSRF secret (false to disable CSRF protection, null to use the global CSRF secret)
41    *
42    * @see sfForm
43    */
44   public function __construct($object = null, $options = array(), $CSRFSecret = null)
45   {
46     $class = $this->getModelName();
47     if (!$object)
48     {
49       $this->object = new $class();
50     }
51     else
52     {
53       if (!$object instanceof $class)
54       {
55         throw new sfException(sprintf('The "%s" form only accepts a "%s" object.', get_class($this), $class));
56       }
57
58       $this->object = $object;
59       $this->isNew = !$this->object->exists();
60     }
61
62     parent::__construct(array(), $options, $CSRFSecret);
63
64     $this->updateDefaultsFromObject();
65   }
66
67   /**
68    * Returns the default connection for the current model.
69    *
70    * @return Connection A database connection
71    */
72   public function getConnection()
73   {
74     return Doctrine_Manager::getInstance()->getConnectionForComponent($this->getModelName());
75   }
76
77   /**
78    * Returns the current model name.
79    */
80   abstract public function getModelName();
81
82   /**
83    * Returns true if the current form embeds a new object.
84    *
85    * @return Boolean true if the current form embeds a new object, false otherwise
86    */
87   public function isNew()
88   {
89     $this->isNew = !$this->object->exists();
90
91     return $this->isNew;
92   }
93
94   /**
95    * Embeds i18n objects into the current form.
96    *
97    * @param array   $cultures   An array of cultures
98    * @param string  $decorator  A HTML decorator for the embedded form
99    */
100   public function embedI18n($cultures, $decorator = null)
101   {
102     if (!$this->isI18n())
103     {
104       throw new sfException(sprintf('The model "%s" is not internationalized.', $this->getModelName()));
105     }
106
107     $class = $this->getI18nFormClass();
108     foreach ($cultures as $culture)
109     {
110       $i18nObject = $this->object->Translation[$culture];
111       $i18n = new $class($i18nObject);
112       unset($i18n['id'], $i18n['lang']);
113
114       $this->embedForm($culture, $i18n, $decorator);
115     }
116   }
117
118   /**
119    * Returns the current object for this form.
120    *
121    * @return BaseObject The current object.
122    */
123   public function getObject()
124   {
125     return $this->object;
126   }
127
128   /**
129    * Binds the current form and save the to the database in one step.
130    *
131    * @param  array      An array of tainted values to use to bind the form
132    * @param  array      An array of uploaded files (in the $_FILES or $_GET format)
133    * @param  Connection An optional Doctrine Connection object
134    *
135    * @return Boolean    true if the form is valid, false otherwise
136    */
137   public function bindAndSave($taintedValues, $taintedFiles = null, $con = null)
138   {
139     $this->bind($taintedValues, $taintedFiles);
140     if ($this->isValid())
141     {
142       $this->save($con);
143
144       return true;
145     }
146
147     return false;
148   }
149
150   /**
151    * Saves the current object to the database.
152    *
153    * The object saving is done in a transaction and handled by the doSave() method.
154    *
155    * If the form is not valid, it throws an sfValidatorError.
156    *
157    * @param Connection An optional Connection object
158    *
159    * @return BaseObject The current saved object
160    *
161    * @see doSave()
162    */
163   public function save($con = null)
164   {
165     if (!$this->isValid())
166     {
167       throw $this->getErrorSchema();
168     }
169
170     if (is_null($con))
171     {
172       $con = $this->getConnection();
173     }
174
175     try
176     {
177       $con->beginTransaction();
178
179       $this->doSave($con);
180
181       $con->commit();
182     }
183     catch (Exception $e)
184     {
185       $con->rollback();
186
187       throw $e;
188     }
189
190     return $this->object;
191   }
192
193   /**
194    * Updates the values of the object with the cleaned up values.
195    *
196    * @param  array $values An array of values
197    *
198    * @return BaseObject The current updated object
199    */
200   public function updateObject($values = null)
201   {
202     if (is_null($values))
203     {
204       $values = $this->values;
205     }
206
207     $values = $this->processValues($values);
208
209     $this->object->fromArray($values);
210
211     // embedded forms
212     $this->updateObjectEmbeddedForms($values);
213
214     return $this->object;
215   }
216
217   /**
218    * Updates the values of the objects in embedded forms.
219    *
220    * @param array $values An array of values
221    * @param array $forms  An array of forms
222    */
223   public function updateObjectEmbeddedForms($values, $forms = null)
224   {
225     if (is_null($forms))
226     {
227       $forms = $this->embeddedForms;
228     }
229
230     foreach ($forms as $name => $form)
231     {
232       if (!isset($values[$name]) || !is_array($values[$name]))
233       {
234         continue;
235       }
236
237       if ($form instanceof sfFormDoctrine)
238       {
239         $form->updateObject($values[$name]);
240       }
241       else
242       {
243         $this->updateObjectEmbeddedForms($values[$name], $form->getEmbeddedForms());
244       }
245     }
246   }
247
248   /**
249    * Processes cleaned up values with user defined methods.
250    *
251    * To process a value before it is used by the updateObject() method,
252    * you need to define an updateXXXColumn() method where XXX is the PHP name
253    * of the column.
254    *
255    * The method must return the processed value or false to remove the value
256    * from the array of cleaned up values.
257    *
258    * @return array An array of cleaned up values processed by the user defined methods
259    */
260   public function processValues($values = null)
261   {
262     // see if the user has overridden some column setter
263     $valuesToProcess = $values;
264     foreach ($valuesToProcess as $field => $value)
265     {
266       $method = sprintf('update%sColumn', self::camelize($field));
267
268       if (method_exists($this, $method))
269       {
270         if (false === $ret = $this->$method($value))
271         {
272           unset($values[$field]);
273         }
274         else
275         {
276           $values[$field] = $ret;
277         }
278       }
279       else
280       {
281         // save files
282         if ($this->validatorSchema[$field] instanceof sfValidatorFile)
283         {
284           $values[$field] = $this->processUploadedFile($field, null, $valuesToProcess);
285         }
286       }
287     }
288
289     return $values;
290   }
291
292   /**
293    * Returns true if the current form has some associated i18n objects.
294    *
295    * @return Boolean true if the current form has some associated i18n objects, false otherwise
296    */
297   public function isI18n()
298   {
299     return $this->getObject()->getTable()->hasTemplate('Doctrine_Template_I18n');
300   }
301
302   /**
303    * Returns the name of the i18n model.
304    *
305    * @return string The name of the i18n model
306    */
307   public function getI18nModelName()
308   {
309     return $this->getObject()->getTable()->getTemplate('Doctrine_Template_I18n')->getI18n()->getOption('className');
310   }
311
312   /**
313    * Returns the name of the i18n form class.
314    *
315    * @return string The name of the i18n form class
316    */
317   public function getI18nFormClass()
318   {
319     return $this->getI18nModelName() . 'Form';
320   }
321
322   /**
323    * Renders a form tag suitable for the related Doctrine object.
324    *
325    * The method is automatically guessed based on the Doctrine object:
326    *
327    *  * if the object is new, the method is POST
328    *  * if the object already exists, the method is PUT
329    *
330    * @param  string $url         The URL for the action
331    * @param  array  $attributes  An array of HTML attributes
332    *
333    * @return string An HTML representation of the opening form tag
334    *
335    * @see sfForm
336    */
337   public function renderFormTag($url, array $attributes = array())
338   {
339     if (!isset($attributes['method']))
340     {
341       $attributes['method'] = $this->isNew() ? 'post' : 'put';
342     }
343
344     return parent::renderFormTag($url, $attributes);
345   }
346
347   /**
348    * Updates and saves the current object.
349    *
350    * If you want to add some logic before saving or save other associated objects,
351    * this is the method to override.
352    *
353    * @param Connection An optional Connection object
354    */
355   protected function doSave($con = null)
356   {
357     if (is_null($con))
358     {
359       $con = $this->getConnection();
360     }
361
362     $this->updateObject();
363
364     $this->object->save($con);
365
366     // embedded forms
367     $this->saveEmbeddedForms($con);
368   }
369
370   /**
371    * Saves embedded form objects.
372    *
373    * @param Connection $con   An optional Connection object
374    * @param array      $forms An array of forms
375    */
376   public function saveEmbeddedForms($con = null, $forms = null)
377   {
378     if (is_null($con))
379     {
380       $con = $this->getConnection();
381     }
382
383     if (is_null($forms))
384     {
385       $forms = $this->embeddedForms;
386     }
387
388     foreach ($forms as $form)
389     {
390       if ($form instanceof sfFormDoctrine)
391       {
392         $form->getObject()->save($con);
393         $form->saveEmbeddedForms($con);
394       }
395       else
396       {
397         $this->saveEmbeddedForms($con, $form->getEmbeddedForms());
398       }
399     }
400   }
401
402   /**
403    * Updates the default values of the form with the current values of the current object.
404    */
405   protected function updateDefaultsFromObject()
406   {
407     // update defaults for the main object
408     if ($this->isNew())
409     {
410       $this->setDefaults(array_merge($this->object->toArray(false), $this->getDefaults()));
411     }
412     else
413     {
414       $this->setDefaults(array_merge($this->getDefaults(), $this->object->toArray(false)));
415     }
416
417     $defaults = $this->getDefaults();
418     foreach ($this->embeddedForms as $name => $form)
419     {
420       if ($form instanceof sfFormDoctrine)
421       {
422         $form->updateDefaultsFromObject();
423         $defaults[$name] = $form->getDefaults();
424       }
425     }
426     $this->setDefaults($defaults);
427   }
428
429   /**
430    * Saves the uploaded file for the given field.
431    *
432    * @param  string $field The field name
433    * @param  string $filename The file name of the file to save
434    * @param  array  $values An array of values
435    *
436    * @return string The filename used to save the file
437    */
438   protected function processUploadedFile($field, $filename = null, $values = null)
439   {
440     if (!$this->validatorSchema[$field] instanceof sfValidatorFile)
441     {
442       throw new LogicException(sprintf('You cannot save the current file for field "%s" as the field is not a file.', $field));
443     }
444
445     if (is_null($values))
446     {
447       $values = $this->values;
448     }
449
450     if (isset($values[$field.'_delete']) && $values[$field.'_delete'])
451     {
452       $this->removeFile($field);
453
454       return '';
455     }
456
457     if (!$values[$field])
458     {
459       // this is needed if the form is embedded, in which case
460       // the parent form has already changed the value of the field
461       $oldValues = $this->getObject()->getModified(true, false);
462
463       return isset($oldValues[$field]) ? $oldValues[$field] : $this->object->$field;
464     }
465
466     // we need the base directory
467     if (!$this->validatorSchema[$field]->getOption('path'))
468     {
469       return $values[$field];
470     }
471
472     $this->removeFile($field);
473
474     return $this->saveFile($field, $filename, $values[$field]);
475   }
476
477   /**
478    * Removes the current file for the field.
479    *
480    * @param string $field The field name
481    */
482   protected function removeFile($field)
483   {
484     if (!$this->validatorSchema[$field] instanceof sfValidatorFile)
485     {
486       throw new LogicException(sprintf('You cannot remove the current file for field "%s" as the field is not a file.', $field));
487     }
488
489     $directory = $this->validatorSchema[$field]->getOption('path');
490     if ($directory && is_file($file = $directory.'/'.$this->getObject()->$field))
491     {
492       unlink($file);
493     }
494   }
495
496   /**
497    * Saves the current file for the field.
498    *
499    * @param  string          $field    The field name
500    * @param  string          $filename The file name of the file to save
501    * @param  sfValidatedFile $file     The validated file to save
502    *
503    * @return string The filename used to save the file
504    */
505   protected function saveFile($field, $filename = null, sfValidatedFile $file = null)
506   {
507     if (!$this->validatorSchema[$field] instanceof sfValidatorFile)
508     {
509       throw new LogicException(sprintf('You cannot save the current file for field "%s" as the field is not a file.', $field));
510     }
511     if (is_null($file))
512     {
513       $file = $this->getValue($field);
514     }
515
516     $method = sprintf('generate%sFilename', $field);
517
518     if (!is_null($filename))
519     {
520       return $file->save($filename);
521     }
522     else if (method_exists($this->object, $method))
523     {
524       return $file->save($this->object->$method($file));
525     }
526     else
527     {
528       return $file->save();
529     }
530   }
531
532   protected function camelize($text)
533   {
534     return sfToolkit::pregtr($text, array('#/(.?)#e' => "'::'.strtoupper('\\1')", '/(^|_|-)+(.)/e' => "strtoupper('\\2')"));
535   }
536 }
537
Note: See TracBrowser for help on using the browser.