Tuesday, September 20, 2011

How to kill Symfony’s Forms and live well

I hate the sfFormDoctrine. I don't make a mystery of it.

If you are reading this article, it's probably because you also have been searching for information on how to make them work, mostly in contests a bit more articulated, like in the case of embedding.
The good news is that you've found the definitive solution.
The bad news is that you have been gasping for months in a glass of water and wasted precious time reading usless chapters of Symfony's manual about Forms.

Very simply: the problems that you always had with the sfFormDoctrine don't depend on you.
It is the very concept of Form to to be wrong.
Now I will show you to get rid of them and live happy.

A dutiful specification

I have a debt of gratitude towards Fabien Pontencier and his team. My business lives because of Symfony. My salary comes from the use of Symfony's framework. Out of all the PHP frameworks that I have worked with professionally, Symfony is without a doubt the best. If I have to tell the whole story, I think Symfony is a masterpiece of architecture.

I do not want to give the impression of disregard towards Fabien's work with what I am about to say. I sincerly admire it.

But from the moment in this article I strongly contest some infrastructural choices of his framework and propose a solution that ruthlessly destroys one of the components ofSymfony's core, I felt like spitting it out: I might have written very strong and extremely politically incorrect assertions.

Forgive me. This is just my personal opinion.

Why so much hate?

I hate the sfFormDoctrine. I don't make a mystery of it.

If you are reading this article, it's probably because you also have been searching for information on how to make them work, mostly in contests a bit more articulated, like in the case of embedding.
The good news is that you've found the definitive solution.
The bad news is that you have been gasping for months in a glass of water and wasted precious time reading usless chapters of Symfony's manual about Forms.

Very simply: the problems that you always had with the sfFormDoctrine don't depend on you.
It is the very concept of Form to to be wrong.
Now I will show you to get rid of them and live happy.

A heretical point of view on the sfFormDoctrine

Where is the sfFormDoctrine in MVC context?

It seems to me that there are at least 3 fundamental tasks:

  1. to execute HTML rendering of the widgets, of the labels and of the error messages
  2. to invoke the methods of the Model – and if necessary the methods of the Models for the embedded Forms in the correct order – in order to persist data on the database
  3. to collect in a sole point the rules of validation and execute them.

Now, feature 1 is the same as the one normally performed by the components of thePresentation Layer, or the View layer in MVC context.

The Forms allow to call back in the template methods such as:
echo $Form->renderHiddenFields(); and to produce HTML code, precisely like link_to().
It is obvious that Forms, therefore, can be placed, together with Helpers, in the View layer of the MVC.

Feature 2 is superimposed to the tasks of the Controller: it's Controller layer elements that typically determine the operating flow of the program and invoke in the right order the methods of the Models of the underlying Data layer.
Forms, therefore, perform typical functions of the MVC Controller.

Feature 3 pertains the manipulation and the correctness of the data conserved from the Business Objects.
It is clearly a task that normally comes turn from the Model.
Forms, therefore, develop typical functions of the MVC Model.

A three-headed monster

The sfFormDoctrine objects unite what the MVC paradigm tried to separate.

In the architecture of SymfonyForms are the sole component whose field of application break the boundaries of the individual layer. The separation of code and functions in M, V and C, one of the cardinal principles of Ruby-On-Rails-like frameworks, comes cheerfully betrayed to breed an allmighty and omniscient monster, that can generate the HTML of the tags, perform the validation of Business Objects and take control of the program execution flow when saving on db. It is the sole object of the whole framework that does not respectMVC principles.

It comes by itself that the typical cleanliness and the exceptional order of softwares written with Symfony are lost as soon as we need to make operations with Forms that are just outside the ordinary. No wonder, in conclusion, if the Embedded Forms are the nightmare of every Symfony developer. And yet, if we think about it, the editing of two tables using just one Form should be a banal operation: or rather, to be really honest, this is the minimum you can ask to a framework.

It is sufficient to follow an ordinary tutorial of CakePHP or Ruby on Rails to realize that this target is really embarassingly very simple to reach with other frameworks. Moreover, both frameworks use exactly the approach described in this article.

If the result is that often the developer has the impression of having almost totally lost the control of his own application, it is clear that there is something really sick insfFormDoctrines.

My conviction is that the fundamental problem is the existence itself of the abstraction layer for the Form management.

The web is full of articles from with titles like 'A workaround to make embedded Forms and merged Forms work in almost any cases (very advanced)'.

I hope I have written 'The definitive suggestion to make the Form matter finally simple'.

Why the situation is not solveable with refactoring or patches

At the end of an umpteenth day of discouragement, working with a dynamically generated embedded Form, after reading the thousandth article on this subject, I have convinced myself to rewrite the entire Form management layer.

Surprisingly, the analysis led me to a rather paradoxical conclusion.

The Business Rules belong to the Business Object!

The first target that I planned was: to transfer the data validation control from the Form to the Model.

The idea that data validation is a Form task always seemed deeply wrong to me. Nowadays I am still convinced that it has been an unforgivable mistake from Symfony architects.
The core mistake is the unreasonable assumption that the data to validate comes unavoidably from a web form.
It is not true and it is very dangerous to assume it.

Let's see an example.

We take an application that should record data for a bank transaction. The application have a TransactionForm form to manipulate the Transaction model with a widget dedicated to the IBAN insertion.

Obviously it is vital that the IBAN is subject to validation prior to storage on db. It's a matter of money. For no reason in the world I would like to have a record with a malformed IBAN.

For this reason, the IBAN widget will have it's trusty validator. According to Symfony, I am covered from any bad surprises.

Nevertheless, if an Action performed:

 $TransAction = new TransAction(); $TransAction->setIban("wrong IBAN"); $TransAction->save(); 

the TransAction Model would cheerfully save the wrong IBAN field without any reports, violating the TransAction object Business Rule.

This possibility is anything but remote: the manipulation of a Model from an Action, without the involvement of a HTML Form, happens for example in the case of webservice.
Besides, it is self-evident: to validate a data means ensuring that the object respects the Business Rules.
And who is responsible for the Business Rules if not the Business Object, that is the Model?

The Business Object is the data. What twisted reasoning could have lead to the delirious decision to deprive the application data core of the sole protection network to move it some meters up, encapsulating inside a component that, among all its tasks, is in charge of printing HTML labels? (Someone, a real purist, could object that the Business Rule should be defined at an even lower level, at the level of RDBMS, for example by means through triggers. How could you say he's wrong?)

Preserving the code to respect the Business Rules within the Form class is dangerous and philosophically wrong. It sensationally violates the Single Responsibility Principle, because encapsulates the Model's responsibility within an object that is just one among many users of the Model and exposes the application to the risk of violating the validation rules every time Models are directly manipulated.

Not only this: it also brings the risk of having different and contradicting valdiation rules in case more than one Form involved the same Model.

The solution, in reality, is very simple: you imperitively need to move the data validation responsibility from users to object to validate.
It's obvious.

It's not an accident, in fact, that any ORM that are worthy of respect gives the tools to perform the validation.
Hibernate supplies the Hibernate Validator. And even Doctrine has it's own good validators. There is even a whole chapter in the manual about this. To ignore it is irresponsible.

For this, I eliminated from my implementation of the sfFormDoctrines every reference to the validation. In my implementation, I wished it was the Model itself to inform the Forms of what Business Rules are violated.

One less task for the sfFormDoctrines, with a great advantage for the solidity of the application.

I hate to double the code in the partial!

Inside the form are listed the widgets that must be used.

In the template that will have to visualize the Form (to understand, in the self-generated partial _Form.php) are listed many

 echo $Form ['a_field']; echo $Form ['an_other_field']; echo $Form ['a_third_field']; 

to obtain the HTML of the input field.

A lot of work can be saved asking the Form to generate itself all HTML inputs, with an

 echo $Form->render(); 

but this, Symfony's fans forgive me, is a solution that's almost ridiculous.

If you leave the control of HTML generation to the Form you need to do deadly leaps to manipulate those features for which the HTML would be the fittest tool.

For example, to move two fields in the HTML you need to intervene in the Form code with

 $this->widgetSchema->setPositions(   array(    'field_1',    'field_3',    'field_2',    'field_4'   ) ); 

Or, if you wanted to use a Form in a second page and decide to hide a field, you would have to implement a new class, adding in its configure ()

 unset($this->widgetSchema['field']) 

to add a Javascript event or to modify a CSS class of an input you need to perform disreputable tricks.

The most astonishing aspect is that Forms do everything to hinder the developer use of the three most suitable tools for these purposes: HTML, Javascript and CSS.

Bad scene.

In fact, echo $Form->render(); is good for scaffolding or little more and if you don't want to lose control too much on the HTML you will have templates substatially equivalent to long sequences of

 [...] <tr>   <th><?php echo $Form['name']->renderLabel() ?></th>   <td>     <?php echo $Form['name']->renderError() ?>     <?php echo $Form['name'] ?>   </td> </tr> <tr>   <th><?php echo $Form['surname']->renderLabel() ?></th>   <td>     <?php echo $Form['surname']->renderError() ?>     <?php echo $Form['surname'] ?>   </td> </tr> [...] 

Now, there would be nothing bad in listing all fields inside the template if it wasn't for the fact that you already have done the same identical job in the Form class with

 $this->setWidgets(array(   'id' => new sfWidgetFormInputHidden(),   'name' => new sfWidgetFormInputText(),   'surname' => new sfWidgetFormInputText(), )); 

Don't Repeat Yourself! Please, I want to write the field list just once!
And, if I could choose, I prefer to do it in the template. Besides, we are writing HTML.

In short, I find that the Form, as a widget container, after all, is not a big help.

I mean, if it was possible to eliminate the widgets from the Form and possible to write them directly in the template with something like:

 [...] <tr>   <td>     Name   </td>   <td>     <?php echo new sfWidgetFormInputText();?>   </td> </tr> <tr>   <td>     Surname   </td>   <td>     <?php echo new sfWidgetFormInputText();?>   </td> </tr> [...] 

we could emancipate the Form from the trivial and totally useless duty of managing HTML in place of the template.

Other frameworks use this approach, supplying the appropriate Helpers.
And even Symfony once supplied a set of Helpers for the tag generation directly in the template.
I got convinced that the template was really the best place where to manage HTML and layout, and that the Form as a generator of HTML was, after all, just an obstacle.

Only one problem remained: to visualize inside the HTML the validation error messages.
But it is soon said: once the Business Rules are transferred inside the Model, the template can query it directly to know which fields are not valid and visualize the respective error messages.
Oh yes, Doctrine can already very well manage by itself a stack of error messages. Is it an accident?

Another useless Form burden that we can get rid of.

No intermediary between sfWebRequest and Business Object!

Let's look at what happens in the typical method executeUpdate() of a Form based Action:

 public function executeUpdate(sfWebRequest $request) {   $this->forward404Unless($request->isMethod(sfRequest::POST) || $request->isMethod(sfRequest::PUT));   $this->forward404Unless($object = Doctrine_Core::getTable('Table')->find(array($request->getParameter('id'))),     sprintf('Object does not exist (%s).', $request->getParameter('id')));   $this->Form = new object Form($object);   $this->processForm($request, $this->Form);    $this->setTemplate('edit');  protected function processForm(sfWebRequest $request, sfForm $Form) {   $Form->bind($request->getParameter($Form->getName()), $request->getFiles($Form->getName()));   if ($Form->isValid())      $object $Form->save(); 

Roughly this is the process:

  1. User performs the submit and the executeUpdate() method, defined as Action in the Form, is invoked.

    $request contains all data introduced from the user

  2. Through Doctrine_Core::getTable('Table')->find($request->getParameter('id')) the object to update is recovered
  3. The object sfDoctrineForm is instantiated and the object instance is passed to it.
  4. sfDoctrineForm is processed. The Form executes the binding between $request and the object, so that the properties of the Model get the values defined in the input fields of the web Form.
  5. The Form is saved. With this operation the object is saved.

The question is: hmmmmm, for what purpose object Form is instantiated?

For two purposes:

  1. To perform the binding between $request and the object
  2. To save the object

It's like firing a cannon at a fly: there is no real need of instantiate an entire class to perform these few operations. You just need to ask the object directly to populate itself according to the data present in $request and subsequently to save itself:

 $object->fromArray($request->getParameter('something'); $object->save(); 

The involvement of the Form, in the standard approach of Symfony, is dictated by the necessity to perform the data validation and from the necessity to save eventual embedded Form. These are, by the way, two tasks that the Doctrine Model is already able to perform extremely well:

  • Model can easily validate itself
  • Model can save recursively the other Models eventually connected to it. Doctrine was invented for this purpose

Let's admit it: in the communication between the sfWebRequest and the Model, theForm is performing some totally useless operations.

But then, what's your purpose?

To sum it up:

  • Form as a HTML generator doubles the code and is not of help
  • It should not be used as a validation tool. May plague attack you if you only think about it.
  • It's no use at all for the communication between Form HTML and Business Object

What functionalities were left to implement, in my new version of the sfDoctrineFormobject?

None.

I began to think that this story of sfFormDoctrines was a large useless bubble. Lots of smoke and no real advantage. Just weeks of headaches to do the easiest operations.

For what benefit, then, to carry this pachyderm?
What would happen if we just simply killed it?
I tried.
It works.
Better.

The Form is dead, long life to Symfony

I tried to pretend I never read the chapter on sfFormDoctrine. And I discovered that everything becomes magically simple.

Let's get to the point. I want a Form (complete with the scary embedding feature) that allows me to save data of a cat: Cat(id, name, owner_id) and of his owner: Owner(id, name).

I used my courage and I killed the directory lib/form.

Goodbye.

After that I wrote a Model, with its good validator (I'm using a banal example: i could use the great standard Symfony validators inherited from sfValidatorBase; we'll see how later)

 class Cat extends BaseCat {   protected function validate() {     // Name's length must be at least 5     if (strlen($this->getName()) < 5) {       $errorStack = $this->getErrorStack();       $errorStack->add('nome', 'Name must be at least 5 char');     }   } } 

validate() is executed when we invoke isValid() to directly query the Model on the vaidity of its own data. The good thing about validate() is that is invoked automatically before saving the record: Doctrine, in other words, inhibits the saving of the records that violate the Business Rules. It's exactly what we were looking for.

Here's the HTML template to print the Form:

 <form method="post" Action="<?php echo url_for("cats/update");?>" >   <table>     <tr>       <td>         <?php            $w = new sfWidgetFormInputHidden();            $w->render("cat[id]", $cat->id);          ?>        </td>        <td>          <?php             $w = new sfWidgetFormInputText();            $w->render("cat[name]", $cat->name);          ?>        </td>      </tr>      <tr>        <td>          <?php            $w = new sfWidgetFormInputHidden();            $w->render("owner[id]", $cat->getOwner()->id);          ?>        </td>        <td>          <?php            $w = new sfWidgetFormInputText();            $w->render("owner[name]", $cat->getOwner()->name);          ?>        </td>      </tr>   </table>   <input type="submit" /> </form> 

The names of the widgets have been chosen so that the Action receives an objectsfWebRequest in the Form of an array, with the Cat data separated from the Owner ones:

 array(   'cat' => array(     'id' => 2,     'name' => "Fritz",   ),   'owner' => array(     'id' => 12,     'name' => "Marianne" ) ); 

The Action Class will look like this:

 class CatsActions extends sfActions {   public function executeNew(sfWebRequest $request) {     $this->cat = new Cat();   }    public function executeUpdate(sfWebRequest $request) {     $cat = new Cat();     $cat->fromArray($request->getParameter('cat'));     $cat->getOwner()->fromArray($request->getParameter('owner'));     if ($cat->isValid() && $owner->isValid()) {       $cat->save();       $this->redirect('cats/index');     }     else {       $this->cat = $cat;       $this->setTemplate('new');     }   }   public function executeEdit(sfWebRequest $request) {     $this->forward404Unless($cat = Doctrine_Core::getTable('Cats')->find(array($request->getParameter('id'))), sprintf('Object does not exist (%s).', $request->getParameter('id')));     $this->cat = $cat;   }   } 

End of the story, more or less.

Please note, among the other things, there's no more need of executeCreate(), because it's by itself capable of managing both the creation and the update of the object.

Don't try to run this code again, it would not work. For now just verify the logic of this.
To make everything run properly there are a couple of arrangements missing, still. These are some setup operations, to execute only once at the creation of the project.

Some Little Tweaks.

Doctrine's validation

In SymfonyDoctrine's validation is deactivated by default.

To activate it you need to set the attribute Doctrine_Core::ATTR_VALIDATE to the valueDoctrine_Core::VALIDATE_ALL.

You can use the callback method configureDoctrine() in the classconfig/ProjectConfiguration.class.php:

 class ProjectConfiguration extends sfProjectConfiguration {   public function configureDoctrine(Doctrine_Manager $manager) {     $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL);   } } 

fromArray() is not clever

When I said that in order to execute the binding between sfWebRequest and the Model is sufficient

 $cat = new Cat(); $cat->fromArray($request->getParameter('cat')); 

I lied a little. Things are a bit more complicated.

Let's suppose we have record 12 with some values inside table cats.

It would be really nice if, loading an instance of cat with the array

 $data = array(   'id' => 12,   'name' => "Fritz" ) $cat = new Cat(); $cat->fromArray($data); 

the following execution of $cat->save() would update record 12.

Unfortunately things work differently and the saving would fail with a duplication error of primary key.

$cat = new Cat() creates a new instance of Cat. For Doctrine, this is a new record. Unfortunately, even once we set the primary key value (either manually or through fromArray()), Doctrine will keep on treating the instance as a new record.

To patch this, we need to be careful to execute $cat->assignIdentifier(12) to tell Doctrine to treat the Model like the id 12 record, and then execute an UPDATE instead of an INSERT.
Naturally, I would never like to have to write some glue code every time that I have to save data of a Form.

The idea is to implement this behavior in a class that extends sfDoctrineRecord. Generalizing the assignment of the primary key is not exactly banal, because the name of the primary key is arbitrarily chosen from the developer and because it is possible that he had chosen to use multiple primary keys. A possible (working) implementation could be:

 abstract class MyDoctrineRecord extends sfDoctrineRecord {   public $validatorSchema;    public function loadFromArray(array $array) {     $primaryKeys = $this->_table->getIdentifier();     if(!is_array($primaryKeys))       $primaryKeys = array($primaryKeys);      $primaryKeys = array_combine($primaryKeys, $primaryKeys);     $intersect = array_intersect_key(array_filter($array),$primaryKeys);      if (count($primaryKeys) == count(array_count_values($intersect))) {       $this->assignIdentifier(array_intersect_key(($array),$primaryKeys));     }     parent::fromArray(array_filter($array));   } } 

There is nothing left to do than convince all the other Table objects of the application to inherit from MyDoctrineRecord instead of sfDoctrineRecord.

Fortunately, Doctrine allows to define the classes to use as a base for both the Business Objects and the Table Classes.

 class ProjectConfiguration extends sfProjectConfiguration {   public function configureDoctrine(Doctrine_Manager $manager) {     $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL);     $options = array(       'baseClassName' => 'myDoctrineRecord',       //'baseTableClassName' => 'myDoctrineTable'     );     sfConfig::set('doctrine_Model_builder_options', $options);   } } 

We just have to use loadFromArray() instead of fromArray() and game over.

And what if we want to reuse sfValidator* classes?

sfDoctrineRecord classes inherit from Doctrine_Record a set of callback methods very useful for the validation: preValidate()postValidate() and validate().
However, it would be very useful to be able to use even the many sfValidator* classes provided from Symfony, like sfValidatorEmail for email validation, sfValidatorDoctrineUniquefor unique indexes validation etc.
It would be interesting, in short, to enrich the sfDoctrineRecord classes with theValidatorSchema previously used by sfDoctrineForm.

Luckily, it is not that difficult.
Let's suppose to define, in Cat Model, one sfValidatorRegex validator associatd to the name of the cat to make sure that the name starts with a capital letter:

 class Cat extends BaseCat {   public function validate() {     parent::validate();     if (strlen($this->nome) < 15) {       $errorStack = $this->getErrorStack();       $errorStack->add('name', 'Too Short');     }   }    public function setUp() {     parent::setUp();     $this->setValidatorSchema(       new sfValidatorSchema(array(         'name' => new sfValidatorRegex(array('pattern'=>'/[A-Z].*/'), array('invalid'=>'The name must start with a capital letter')       )      ));   } } 

We want that during the validation, not just the standard method sfDoctrineRecord::validate() is executed but also all the validators defined inside the Validator Schema.

It will be sufficient to enrich our implementation of sfDoctrineRecord as follows:

 abstract class MyDoctrineRecord extends sfDoctrineRecord {   public $validatorSchema = array();   public function setValidatorSchema(sfValidatorSchema $validatorSchema) {     $this->validatorSchema = $validatorSchema;   }   public function getValidatorSchema() {     return $this->validatorSchema;   }   public function validate() {     $this->setup();      try {       $this->getValidatorSchema()->clean($this->toArray(false));     }     catch(sfValidatorErrorSchema $errorSchema) {       $errorStack = $this->getErrorStack();       foreach($errorSchema->getErrors() as $key=>$error) {         /*@var $error sfValidatorError */         $errorStack->add($key, $error->getMessage());       }     }   } } 

Some Helpers

In sfDoctrineRecord::getErrorStack() the Model supplies an array with the validation errors.

sfDoctrineRecord::getErrorStack()->get($fieldName) supplies an error with the various errors spotted in the field of $fieldName Model.

To get the various messages from the Model and visualize them inside the Template we need a cycle similar to:

 foreach($Model->getErrorStack()->get($fieldName) as $error) {   echo $error; } 

It's worthy to use a Helper to reduce the code to be typed into the template. A possible prototype could be this one:

 class MyRender {   static function error($Model, $name) {     if(count($Model->getErrorStack()->get($name))>0)       return $Model->getErrorStack()->get($name);     else       return null;   }   static function errorList($Model, $name) {     $list = self::error($Model, $name);     if(!is_array($list))       $list = array($elenco);     return implode("<br/>", $list);   } } 

This way the template can be renamed as follows

 <tr>   <td>Name</td>   <td>     <?php echo MyRender::errorList($cat, 'name');?>     <?php     $w = new sfWidgetFormInputText();   	echo $w->render('cat[name]', $cat->getName();     ?>   </td> </tr> 

Download

Example for the lazy

You can download from here a full project that uses this approach.

And what about the automatic generation of the code?

Ephraim Pepe wrote a Symfony task that is able to generate Actions and templates based on this concept.

His work will be soon published.

What do we miss?

Just the sfForm class in the standard library of Symfony is made of 1340 lines of code. InSymfony 2 we still have a class hierarchy inside Symfony\Component\Form namespace with over 5000 lines of code already developed.

I have doubts, therefore, that in the next days, going deeper inside the subject, I will discover some fundamental functionalities that are not covered from my approach. For example, I know for sure that I still have to work on the implementation of the CSRF protection.

Yet I am persuaded that the direction to follow is the right one: leaving templates in control of the HTML, moving validation to Business Objects, using only the minimum necessary intermediates between sfWebRequest and Model.

In the company that I work for we are progressively abandoning the use of sfDoctrineForms in favor of this approach even for our production softwares.

If you are interested in the subject, stay in touch: we will be sharing code as we go.

Symfony: Merge embedded Form (Update)

Symfony provides a nice feature called "embedded Forms" (sfForm::embedForm) to embed subforms into a parent form. This can be used to edit multiple records at the same time. So let's say you have a basic user table called 'sf_guard_user' and a profile table called 'user_profile', then you might follow this guide to merge these forms together:

lib/forms/doctrine/sfUserGuardAdminForm.php:

 class sfGuardUserAdminForm extends BasesfGuardUserAdminForm {   public function configure()   {     parent::configure();      // Embed UserProfileForm into sfGuardUserAdminForm     $profileForm = new UserProfileForm($this->object->Profile);     unset($profileForm['id'], $profileForm['sf_guard_user_id']);     $this->embedForm("profile"$profileForm);   }2 }  

Remember to add "profile" to the list of visible columns inapps/backend/modules/sfGuardUser/config/generator.yml as decribed in the linked guide. The result may look like this:

Embedded form in symfony

This does what it is expected to do, but it doesn't look very nice. Especially for 1:1 related tables I'm more interested in a solution that looks like this:

Merged forms in Symfony

You can reach this using sfForm::mergeForm, but sadly the merged model won't get updated and you'll run into problems if the forms are sharing fieldnames. The solution is the following method embedMergeForm which can be defined in BaseFormDoctrine to be avaible in all other forms:

lib/forms/doctrine/BaseFormDoctrine.php:

 abstract class BaseFormDoctrine extends sfFormDoctrine {   /**    * Embeds a form like "mergeForm" does, but will still    * save the input data.    */   public function embedMergeForm($namesfForm $form)   {     // This starts like sfForm::embedForm     $name = (string) $name;     if (true === $this->isBound() || true === $form->isBound())     {       throw new LogicException('A bound form cannot be merged');     }     $this->embeddedForms[$name] = $form;      $form = clone $form;     unset($form[self::$CSRFFieldName]);      // But now, copy each widget instead of the while form into the current     // form. Each widget ist named "formname|fieldname".     foreach ($form->getWidgetSchema()->getFields() as $field => $widget)     {       $widgetName "$name|$field";       if (isset($this->widgetSchema[$widgetName]))       {         throw new LogicException("The forms cannot be merged. A field name '$widgetName' already exists.");       }        $this->widgetSchema[$widgetName] = $widget;                           // Copy widget       $this->validatorSchema[$widgetName] = $form->validatorSchema[$field]; // Copy schema       $this->setDefault($widgetName$form->getDefault($field));            // Copy default value        if (!$widget->getLabel())       {         // Re-create label if not set (otherwise it would be named 'ucfirst($widgetName)')         $label $form->getWidgetSchema()->getFormFormatter()->generateLabelName($field);         $this->getWidgetSchema()->setLabel($widgetName$label);       }     }      // And this is like in sfForm::embedForm     $this->resetFormFields();   }    /**    * Override sfFormDoctrine to prepare the    * values: FORMNAME|FIELDNAME has to be transformed    * to FORMNAME[FIELDNAME]    */   public function updateObject($values null)   {     if (is_null($values))     {       $values $this->values;       foreach ($this->embeddedForms AS $name => $form)       {         foreach ($form AS $field => $f)         {           if (isset($values["$name|$field"]))           {             // Re-rename the form field and remove             // the original field             $values[$name][$field] = $values["$name|$field"];             unset($values["$name|$field"]);           }         }       }     }      // Give the request to the original method     parent::updateObject($values);   } }  

This method ensures that each fieldname is unique (named 'FORMNAME|FIELDNAME') and the subform is validated and saved. It is used like embedForm:

lib/forms/doctrine/sfUserGuardAdminForm.php:

 class sfGuardUserAdminForm extends BasesfGuardUserAdminForm {   public function configure()   {     parent::configure();      // Embed UserProfileForm into sfGuardUserAdminForm     // without looking like an embedded form     $profileForm = new UserProfileForm($this->object->Profile);     unset($profileForm['id'], $profileForm['sf_guard_user_id']);     $this->embedMergeForm("profile"$profileForm);   } }  

Feel free to use this method in your own project. Maybe this method get's merged into Symfony some day ;-)

Update
frostpearl reported a problem using embedFormMerge() in conjunction with the autocompleter widget from sfFormExtraPlugin. If you expire these problems try to replace all occurences of "$name|$field" with "$name-$field".

Let's Play with Symfony 1.2 and Doctrine

It's been quite a long time I didn't give a go to Doctrine, so as it's gonna be bundled by default in with the upcoming 1.2 release of symfony, I thought it was a good occasion to play with it.

So let's checkout the 1.2 SVN branch of symfony and create a test project with a main application[1]:

 $ mkdir sf12test && cd sf12test $ mkdir -p lib/vendor $ svn co http://svn.symfony-project.com/branches/1.2 lib/vendor/symfony $ php lib/vendor/symfony/data/bin/symfony generate:project sf12test $ ln -s ../lib/vendor/symfony/data/web/sf web/sf $ ./symfony generate:app main

Create a webserver vhost pointing to the web folder of the project directory. I've already explained plenty of times how to achieve this step.

Now, let's enable the sfDoctrinePlugin and disable the Propel one by editing the setup() method of theconfig/ProjectConfiguration.class.php file:

  php   public function setup()   {     $this->disablePlugins('sfPropelPlugin');     $this->enablePlugins('sfDoctrinePlugin');   } 

You can list the available tasks running this simple command:

 $ ./symfony list doctrine

Managing the Database Schema

First, configure your config/databases.yml file to set the database connection parameters. If you want to quick test Doctrine, use a local SQLite db, like this:

  yaml all:   doctrine:     class:    sfDoctrineDatabase     param:       dsn:    sqlite://<?php echo dirname(__FILE__).'/../data/data.db' ?> 

We're going to make a very simple weblog application, so let's configure our database schema. We can do it in YAML[2], so fire up your favorite editor/IDE and edit a brand new config/doctrine/schema.yml:

  yaml BlogPost:   actAs:     Sluggable:       fields:       [title]     Timestampable:   columns:     title:          string(255)     body:           clob     author:         string(255)  BlogComment:   actAs:            [Timestampable]   columns:     blog_post_id:   integer     author:         string(255)     email:          string(255)     content:        clob   relations:     BlogPost:       class:        BlogPost       local:        blog_post_id       foreign:      id       foreignType:  many       type:         one 

Note that Doctrine offers several pretty cool features including native behaviors (timestampable and slugable are used here).

Now, create a data/fixtures folder and put a data.yml file in, containing some test data in YAML format:

  yaml BlogPost:   p1:     title: My first post     body: |       This is cool.     author: NiKo     created_at: "<?php echo date('Y-m-d H:i:s', time() - 86400) ?>"   p2:     title: My second post     body: |       This is still cool.     author: NiKo     created_at: "<?php echo date('Y-m-d H:i:s', time() - 7200) ?>"   p3:     title: Third post     body: |       Is this one cool?     author: Roger Hanin     created_at: "<?php echo date('Y-m-d H:i:s') ?>"  BlogComment:   c1:     BlogPost: p3     author: John     email: john@doe.com     content: Hey, you're right there.     created_at: "<?php echo date('Y-m-d H:i:s', time() - 86400) ?>"   c2:     BlogPost: p3     author: Paul     email: paul@doe.com     content: Nope, he's not.     created_at: "<?php echo date('Y-m-d H:i:s') ?>" 

Okay, now run the command below to generate the needed files, create the database and fill it with the data fixtures:

 $ ./symfony doctrine:build-all-load

We can run several DQL queries in command line to check if everything is fine. DQL is very powerful, and compatible with a lot of RDBMS. You'll find more information on DQL on the doctrine website.

For example, to find all blog posts:

 $ ./symfony doctrine:dql "From BlogPost p" found 3 results -   id: '21'   title: 'My first post'   body: "This is cool.\n"   author: NiKo   slug: my-first-post   created_at: '2008-10-29 15:14:25'   updated_at: '2008-10-30 15:14:25' -   id: '22'   title: 'My second post'   body: "This is still cool.\n"   author: NiKo   slug: my-second-post   created_at: '2008-10-30 13:14:25'   updated_at: '2008-10-30 15:14:25' -   id: '23'   title: 'Third post'   body: "Is this one cool?\n"   author: 'Roger Hanin'   slug: third-post   created_at: '2008-10-30 15:14:25'   updated_at: '2008-10-30 15:14:25' 

Another example, to find informations about the blog post with slug third-post and its associated comments:

 $ ./symfony doctrine:dql "Select p.title, p.author, c.author, c.content From BlogPost p, p.BlogComment c Where p.slug = 'third-post' Group by c.id" found 3 results -   id: '23'   title: 'Third post'   author: 'Roger Hanin'   BlogComment: [{ id: '15', author: John, content: 'Hey, you''re right there.' }, { id: '16', author: Paul, content: 'Nope, he''s not.' }] 

Put the Query Logic in the Model

The Model part of any MVC architecture must contains the business data and associated logic. In other words, these data and logic should never be handled anywhere else, to decouple your components at max. So we'll add some query methods in thelib/model/doctrine/BlogPostTable.class.php file, which represents our blog_post table and available operations on it:

  php <?php class BlogPostTable extends Doctrine_Table {   public function getAll()   {     return Doctrine_Query::create()->       select('p.title, p.slug, p.body, p.author, p.created_at, count(c.id) numcomments')->       from('BlogPost p, p.BlogComment c')->       orderBy('p.created_at DESC')->       groupBy('p.id')->       execute();   }    public function getOneBySlug($slug)   {     $posts = Doctrine_Query::create()->       from('BlogPost p')->       leftJoin('p.BlogComment c')->       where('p.slug = ?')->       orderBy('c.created_at ASC')->       limit(1)->       execute(array($slug));      return isset($posts[0]) ? $posts[0] : null;   } } 

A Weblog is About Web Interface, uh?

Okay, let's add pretty controllers and templates to give some life to our blog. First, generate a post module in the main app:

 $ ./symfony generate:module main post

Then, edit the apps/main/modules/post/actions/actions.class.php file:

  php <?php class postActions extends sfActions {   public function executeIndex($request)   {     $this->posts = Doctrine::getTable('BlogPost')->getAll();   }      public function executeShow($request)   {     $this->post = Doctrine::getTable('BlogPost')->getOneBySlug($slug = $request->getParameter('slug'));     $this->forward404Unless($this->post, 'No post with slug=' . $slug);     $this->comments = $this->post->getBlogComment();   } } 

We should have display templates too. The first one will show the posts list, inapps/main/modules/post/templates/indexSuccess.php:

  php <?php foreach ($posts as $post): ?>   <?php include_partial('post/post', array('post' => $post, 'numComments' => $post->getNumcomments())) ?>   <hr/> <?php endforeach; ?> 

Note that we must create the _post partial template, in apps/main/modules/post/templates/_post.php:

  php <h2><?php echo link_to($post->getTitle(), 'post/show?slug='.$post->getSlug()) ?></h2> <p>   <small>Posted by <?php echo $post->getAuthor() ?> on <?php echo $post->getCreatedAt() ?>   <?php if (isset($numComments)): ?>     - <?php echo $numComments ?> comments   <?php endif; ?>   </small> </p> <?php echo $post->getBody(ESC_RAW) ?> 

The other main template will display one post and its comments, in apps/main/modules/post/templates/showSuccess.php:

  php <?php include_partial('post/post', array('post' => $post)) ?>  <h2>Comments</h2> <?php if (!count($comments)): ?>   <p>No comment yet.</p> <?php else: ?> <?php foreach ($comments as $comment): ?>   <p><small>By <?php echo $comment->getAuthor() ?> on <?php echo $comment->getCreatedAt() ?></small></p>   <blockquote><?php echo $comment->getContent() ?></blockquote> <?php endforeach; ?> <?php endif; ?> 

That's it. A rough but functional weblog if you lauch your browser to yourhost/main_dev.php/post/index:

step2.png

And if you click a post title:

step1.png

Good News, the Forms Framework Works with Doctrine Too

Symfony 1.1 introduced the new forms framework, and good news, Doctrine can take part of it. So maybe you've already noticed it, we have form classes generated already, in the lib/form/doctrine folder of the project.

So let's add a neat commenting system to our blog, by first editing the lib/form/doctrine/BlogCommentForm.class.php file:

  php <?php class BlogCommentForm extends BaseBlogCommentForm {   public function configure()   {     unset($this['id'], $this['created_at'], $this['updated_at']);          $this->widgetSchema['blog_post_id'] = new sfWidgetFormInputHidden();          $this->validatorSchema['author']  = new sfValidatorString(array('min_length' => 3));     $this->validatorSchema['email']   = new sfValidatorEmail();     $this->validatorSchema['content'] = new sfValidatorString(array('min_length' => 5));   } } 

Now, use the form in the executeShow() method of our controller:

  php <?php // ...   public function executeShow($request)   {     $this->post = Doctrine::getTable('BlogPost')->getOneBySlug($slug = $request->getParameter('slug'));     $this->forward404Unless($this->post, 'No post with slug=' . $slug);     $this->comments = $this->post->getBlogComment();          $comment = new BlogComment();     $comment->setBlogPost($this->post);     $this->form = new BlogCommentForm($comment);          if ($request->isMethod('post') && $this->form->bindAndSave($request->getParameter('blog_comment')))     {       $this->redirect('post/show?slug='.$this->post->getSlug());     }   } 

And in the showSuccess.php template, we'll append the form display:

  php <h3>Add a comment</h3>  <?php echo $form->renderFormTag(url_for('post/show?slug='.$post->getSlug())) ?>   <table>     <?php echo $form ?>     <tr>       <td></td><td><input type="submit"/></td>     </tr>   </table> </form> 

We've now a pretty commeting system added to our blog, thanks to all the goodness provided by symfony and Doctrine:

step3.png

Conclusion

The time when everyone choosed Propel because it was more stable than Doctrine seems to be over. Doctrine is robust, and performs quite well on my box. Furthermore, it handles complex relationships and dynamic object hydratation natively and better than Propel. Doctrine is also very well integrated into symfony, certainly because Jonathan Wage - the Doctrine lead developer - now works for Sensio, creator and main sponsor of symfony.

Wednesday, August 10, 2011

symfony bridge wordpress, share cookie for domain and subdomain, Sharing cookies among all subdomains

There's a common problem of sharing cookies for domain and subdomain.
solution is set domain with www.example.com and set cookie domain to '.example.com'
reference: 


Sharing cookies among all subdomains


As explained earlier, cookies are not shared among subdomains or between the domain
and the subdomain. In order to set cookies accessible by all subdomains, use the 
following techniques:

  1. While writing the cookie, set the cookie domain to ".domain.ext" so that it applies 
    to all subdomains.

  2. If the cookie domain is set to ".domain.ext", it will not be accessible by a user
    who types in the address without the www before the domain (i.e. http://domain.ext).
    Therefore, redirect all requests without www to http://www.domain.ext.


There are some reported problems with the above approach. It is safe to set the default cookie 
with no domain specified and then set another one with domain as ".domain.ext". In this case 
there is no need for the redirects.
However, remember that session cookies are set by the web server software and you may not
have control over how the cookie domain is set.

real world case:

symfony site on example.com
blog is wordpress on blog.example.com
both sharing same top navigation, so, in blog, the login/logout link should be switched by user current status.
to achieve this, in blog.example.com , we need to know if user has logged in at example.com

steps:

1, set permanent redirect, redirect all example.com request to www.example.com

2, in sfGuardSecurityUser class, add

    // set a cookie for all subdomains
    sfContext::getInstance()->getResponse()->setCookie('wp_bridge', $this->generateRandomKey(), time() + $expiration_age, '/', '.example.com');

at bottom of signIn method

  add

sfContext::getInstance()->getResponse()->setCookie('wp_bridge', '', time() - $expiration_age, '/', '.example.com');

at bottom of signOut method


3, in wordpress

in my case, find the file : wp-content/themes/mytheme/custom/custome_functions.php

find cookie by

echo 'Hello '.($_COOKIE['wp_bridge']!='' ? $_COOKIE['wp_bridge'] : 'Guest');



DONE, enjoy the bridge.

Monday, August 08, 2011

Friday, May 13, 2011

网上问诊单

     首先声明,如有以下十一种人之一者请不要填写:

1、不信中�的人 .

2、���有疑�的人 .

3、自以�是的人 , 又要吃西�又要吃中�的人 .

4、懂一���中� , ����方指指��的人 .

5、拿到�方 , 不按�吃的人 .

6、拿到�方 , 到了�房抓� , ����此方太� , ��不行 , 要加那��等 , 立刻就�定不拿�了 , 或回������方�� , 此�笨蛋不要治 , �於��草型 , 完全不知道自己在作什� . ��不是�� , 他干���治� , �也信 , �在有�笨拙 .

7、不���建�的人 , 以�吃�就好了 , 比如: 肝硬化是由喝酒引起 , 吃中�後 , 仍然不戒酒 , 如何�好呢?

8、重��同一��的人 , 表示他不相信你了 . 不可治 . (我在看病�都ㄧ定��解�病情�病人�, 但是再次���病人居然�� , 吃��是�什�? 你��不�人 , 不相信就不要�看中� , 何必如此害自己呢?)

9、一有重病 , 只要人�什�好 , 就立刻去做 , �果�吃一大堆� , 此�有疾病�投�的人不要治 .

10、平�吃一大推不必要的�或�他命的人 , 生病��看�� , �身��一�囊 , ��求����何�可吃 , 何�不可吃 , ��人除了本身疾病外 , 又自己增添�多本�根本不存在的����生 , 千�不要看 .

11、吃药有效 , 不坚�吃的人

以上十一�人 , 奉�所有中�� , 千�不要去治 , 此�病人你就算是治好也不�感�你 , 他���你是蒙到的 , 治不好就都是你的� , �不�去���什�自己�得到此病 , �人�什��有此病 , 只�怪�生不好 . 但是如果他�是去看西�� , �度就一百八十度��了 , 完全�西�的� , 就像一�口袋中的�物一� , 主人�食什�就吃什� , 乖巧的要死 , 就算�果是死於西�之手 , 他�也不�去怪罪西� , 家����在抬病患��出院� , 列�向西�致�意的 ..

中医网上诊病,失去了四诊的望、闻、切三诊,为准确诊断,有效治疗,请问病患者详细填写下面内容(连同注意、说明全部复制,以便查阅),有则选择,无则删除。

1、请详细填写下列条目——

性别:

生辰:(公元)年月日时

籍贯: 

婚否:

职业:

身高:

体重: 

腰围:

肤色:

主要症状:

发病经过:

治疗经过(尽量详细一点):

西医病名:

是否长期用过激素:

是否大量用过消炎药:

是否做过内脏手术:

2、请选择下列部位身体状态(将所无状态删除)——

脉(左:快,慢,有力,无力,粗,细,浮,沉,松,紧,停顿,时快时慢)

(右:快,慢,有力,无力,粗,细,浮,沉,松,紧,停顿,时快时慢)

面(红,黄,白,黑,青):

眼(瞳仁色灰;白睛色青,色黄;有血丝,有瘀斑,有隆起,有黑点,眼下色青,眼皮肿):

舌(肥大,瘦,尖红,滑湿,有齿痕,干,苔腻,黄苔,白苔,中有裂纹,舌下筋脉曲张):

齿(整齐,龃龉,有缝,颜色,大,长,齿痛,活动,有斑,无力)

耳(鸣,聋,痒,痛,流水,左,右):

目(干涩,昏花,眩晕,流泪,痛,痒,左,右):

口(苦,干,酸,无味,溃疡,流涎):

鼻(不通,流涕,凉,热,左孔,右孔):

咽(干,痛,痒,吞咽困难):

胸(满,闷,胀,痛,骨痛,热,凉):

乳(胀,痛,增生,肿块,左,右):

胁(胀,痛,热,凉,左,右):

腹(胀,满,痛,肿,硬):

外阴(肿,胀,痛,热,凉,如烧,坠胀,挛缩):

心(烦,悸,慌,紧,痛,凉,热,跳快):

心下(满,闷,堵,胀,烧):

胃(胀,痛,满,酸,凉,热):

头(痛,晕,麻,木,昏,沉重):

项(僵,痛,酸重,不能转动):

肩(痛,凝,沉重,萎缩,凉,热,左,右):

臂(痛,麻,木,胀,酸,颤,凉,热,左,右):

手(凉,热,干,出汗,麻,颤,指痛,甲痛,萎缩,左,右):

背(痛,酸,重,胀,凉,热,左,右):

腰(酸,重,痛,胀,凉,热,左,右,中间):

腿(痛,酸,重,麻,木,肿,胀,僵,软,凉,热,左,右,内侧,外侧):

膝(痛,肿,凉,热,软,骨痛,无力,左,右):

足(痛,胀,麻,木,软,凉,热,左,右):

跟(痛,凉,热,麻,胀,左,右):

足趾(麻,痛,胀,凉,热,左,右,部位):

3、请选择与下列项目对应的身体状况(必选)——

饮(多,少,喜凉,喜热,喜温,喜酸,喜辣,喜苦,喜咸,喜甜):

食(多,少,喜米,喜面,喜粥,喜油腻,厌油腻):

睡眠(多,少,失眠,多睡,多梦,易醒):

大便(干,湿,粘,便秘,失禁,色黑,色黄,色白,溏泻,一日一次,一日数次,数日一次):

小便(多,少,不畅,不禁,色黄,色白,次数多,次数少):

房事(有,无,多,少,阳亢,阳痿,早泄,手淫,曾堕胎):

月经(先期,后期,多,少,暗,红,淡,浓稠,清稀,有血块,痛经,漏泻):

白带(有,无,多,少,白,黄,腥,臭):

4、请回答身体经常有如下哪些症状——

(发热,出汗,怕冷,怕风,怕热,哮,喘,咳,气短,有痰,打嗝,呕逆,恶心,腹泻,便血,尿血,吐血,鼻血,牙龈出血,烦躁,肢体震颤,肢体僵硬,乏力倦怠,头重脚轻,少气懒言,嗜卧,厌卧,半身不遂,时冷时热,长期低烧,淋巴节肿大,胸骨刺痛,站立觉累,静脉曲张,骨发空,骨发热,梦遗,早泄,阳痿,不孕)

5、七情何者为重——

(喜悦,愤怒,忧郁,思虑,悲伤,恐惧,惊悸)

如何知道你的身体是否健康,有以下五通标准。

饮食通:不偏食,有良好的嗅觉、味觉及食觉。口润不渴,亦可及时补充水分。

休息通:休息每日在七八个小时左右,午时有一小时的午睡时间,夜有梦但不多,睡眠深。醒后精力充沛。

大小便通:大小便正常,每日晨起有一次大便,不干不稀,通畅,五分钟可解决问题。小便清,无泡沫,无杂质。小便与饮水量成正比。

体力通:体力充沛,能够胜任一般劳动,不感到疲劳,过重体力劳动,半小时左右能休息过来

阳气通:身体温暧,手足温暧,身不畏寒,无上火现象,有正常的性能力。     

公布秘方,比一比,看谁的【生肌散】效果更好?(zhuanzai)

公布秘方,这是我用了十多年的生肌散,效果很好,公开给大家,欢迎各位拿秘方来比较效果高低!

人中白(炙)30g  象皮(砂炒泡)50g  乳香30g  滑石粉30g  血竭30g  白芨30g

共研细末,撒于创口处

主治:一切伤口、溃疡,久不愈合,效神速!


Thursday, May 05, 2011

肥胖的原因与减肥

    人体肥胖的原因,一是身体的垃圾不能及时清理,堆集在细胞和组织空隙,或与人体脂肪同处,垃圾和它所吸收的水分增加了人的体重,并使人的精神倦怠,气色黄浊。本质的原因则是年龄大或疾病造成的脏腑功能减弱,新成代谢不足及内分泌不调所引起;二是饮食不节,营养过剩,又因阳气不足,不能将多余的营养转化为动能,或转化为元精收藏(纳气归肾或曰潜阳)。反而使之化为脂肪存贮于人体组织间隙或体内空处。

减肥的方法一般有节食、运动和药物治疗等。节食对营养过剩引起的肥胖有一定作用,但对新成代谢不足或内分泌不调引起的肥胖效果不佳,且节食不能太过,否则影响人体的正常能量供给;运动有助于过剩营养的消耗并排除一些体内垃圾,但运动同时也促进代谢垃圾的产生,人如果阳气不足,运动消耗的阳气不能及时恢复和补充,运动增加的垃圾不能及时排除,反而使身体更差而益增肥胖。所以运动减肥只能针对内体较好而缺乏运动的人。但不管运动或节食,都很辛苦,大多数人都不能坚持,而且坚持了也不一定见效,所以人们更期望药物减肥。目前,国内外药物减肥的方法很多。一是厌食法,服用一些药物,让人不想吃东西,产生减肥的作用。肥是减了但对人身体的破坏很大,可谓本未倒置,得不偿失。且一旦不服药,肥胖又会回来。二是脱水法或曰排毒法。即服用具有泄泻作用的药物,使人增加排泄而减重。实际上只是排水,即减去了人体细胞和组织包括垃圾与脂肪的水分,毒(拉圾)和脂肪并没少,却会造成人正常生理需要的水分也不能满足,而影响了人的身体健康。停药后,垃圾与脂肪的水分得到补充,重量又回来了。且因人的身体变弱,会产生更多的垃圾和脂肪,人的体重迅速反弹,并将逐渐超过服药之前。

    正确的减肥方法只有一种,即保护和提升人体的阳气(即元气)。人体的阳气增加后,排毒(垃圾)功能和利用营养能力增强,产生和积聚的垃圾与脂肪少,并能排除积存的体内垃圾和燃烧利用体内脂肪,故能减肥。现代人普遍阳气不足。原因包括:夜生活过度,饮酒过度,纵情过度,焦虑过大,喜食冷饮,动辄服用清热药物和抗生素(寒性)等等。只有尽量克服以上毛病,才能保护好自身阳气。提升人体阳气的方法有:1、内体好而运动少的人可选择运动减肥(运动可增加人的阳气水平)。2、药物调节人体功能,扶助阳气,使清气能升,浊气能降。药方:

    党参15克、白术15克、干姜15克、甘草10克、肉桂8克、山楂肉15克。煎汤服用。(因病引起的肥胖,当先解决病根,再谈减肥。)

    服药后出现头晕,身酸无力,排便增加等等不舒服状,都是正常的。是在排垃圾,排完就好了。到时你发现你很容易的就这样减了肥,没有付作用不说,而且还美了容,健了身。感到身轻体健、饮食有味、睡眠加深、气色变好、工作精力增强、心情倍加愉悦。

    赞曰:莫道减肥难,岐路千万般;一旦方法对,玉环变飞燕。 

    补充说明:本减肥方可以长期吃,有双向调节的作用,可使胖者变瘦,瘦者变胖;有肥减肥,无肥强身。开始时排浅层毒素,减肥速度较快。但过后一段时间,可能反而会有增加体重或体积的现象,但不必惊奇,这是深层垃圾吸收水分造成的。继续吃药,垃圾就会排出,从而达到真正减肥而不反弹的目的。