Article

Learn symfony: a Beginner's Tutorial

Page: 1 2 3 4 5 6 7 Next

Modifying the Model

So far, there hasn't been much PHP code in this tutorial. That's one of the strengths of symfony: it doesn't force you to write code that symfony can generate itself based on simple data. However, you will need to write a few lines of PHP for the tags in the model. First, let's explore the model's structure.

The propel-build-all command generated two files for each table in the lib/model/ directory. For instance, for the photo table, the generated model files are Photo.php and PhotoPeer.php. If you look at their code, they actually contain empty classes that inherit from other classes (BasePhoto and BasePhotoPeer) located in the lib/model/om/ directory. For instance, the generated lib/model/Photo.php content is as follows:

<?php  
/**  
* Subclass for representing a row from the 'photo' table.  
*  
*    
*  
* @package lib.model  
*/    
class Photo extends BasePhoto  
{  
}

When you build the model with the command line, only the classes in the lib/model/om/ directory are modified. Have a look at them to see the amount of code that's been generated. The classes in lib/model/ are never altered by the code generator (which just creates their empty shell the first time); to extend the model, you should write your methods in these empty classes. This mechanism allows you to extend the model classes without risking the loss of your modifications should you decide to change the relational schema and build the model again. The model class system is both extensible and scalable.

We split the generated code into two classes (Photo and PhotoPeer) to differentiate between the methods linked to individual objects, and the methods linked to the class, but not its objects -- or, if you prefer, to differentiate between object and static methods. The PhotoPeer class contains only static methods that are used to retrieve objects of class Photo. You will soon understand how it works.

We want to have the ability to add tags to, or delete tags from, a photo. The foreign key between the photo and tag tables (the photo_id column in the tag structure) is instantiated in the Photo object by a generated getTags method. Symfony generated this method automatically and placed it in the BasePhoto class so that the Photo class can use it as well. So, if you have a Photo object, you can get its related tags like this:

$tags = $photo->getTags();

That's right, there's no need to call an SQL query with a WHERE clause -- the generated Base classes do it automatically. We'll use the same principle to add new methods to the Photo class. Open the Photo.php file and add the following methods:

// in lib/model/Photo.php  
class Photo extends BasePhoto  
{  
 public function getTagsString()  
 {  
   $tags = array();  
   foreach ($this->getTags() as $tag)  
   {  
     $tags[] = $tag->__toString();  
   }  
   return implode(' ', $tags);  
 }  
   
 public function setTagsString($tagPhrase)  
 {  
   // remove old tags  
   $this->deleteTags();  
     
   // set new tags  
   $tagNames = explode(' ', $tagPhrase);  
   foreach($tagNames as $tagName)  
   {  
     $tag = new Tag();  
     $tag->setPhoto($this);  
     $tag->setName($tagName);    
     $tag->save();  
   }  
 }  
   
 public function deleteTags()  
 {  
   $c = new Criteria();  
   $c->add(TagPeer::PHOTO_ID, $this->getId());  
   TagPeer::doDelete($c);  
 }  
}

There's a lot to explain in these three methods. First, as you saw previously, the generated getTags method returns an array of Tag objects. Returning the tags of a photo as a string is no harder than calling the implode function.

Second, the setTagsString method creates Tag objects from a string, and relates them to the current Photo object. This is a good illustration of how to manipulate the fields of a record in a related object using the dynamic setter methods, which were also generated by the propel-build-all command. The call to the save method triggers an INSERT query to the database to create a record based on the properties of the object. Last, the deleteTags method calls static constants and methods in the TagPeer class, as well as the add method in the Criteria object. We won't describe these methods now -- you just need to understand that the doDelete call triggers a DELETE query in the database to remove all tags related to the current Photo.

One last thing: for the getTagsString method to work, an object of class Tag must be able to be output as a string. We can achieve this by adding a magic __toString method to the Tag class, as follows:

// in lib/model/Tag.php  
class Tag extends BaseTag  
{  
 public function __toString()  
 {  
   return $this->getName();    
 }  
}

Adding a new getter and a new setter is enough to simulate a new column. This means that we can add the tags_string column in the display arrays of our generator.yml as if it were an actual field. That's the beauty of an object model:

// in apps/frontend/modules/photo/config/generator.yml  
generator:  
 class:              sfPropelAdminGenerator  
 param:  
   model_class:      Photo  
   theme:            default  
   list:  
     display:        [_photo, description, tags_string, created_at]  
     object_actions:  
       _edit:  
         name:       Edit picture properties  
   edit:  
     display:        [_photo, file_path, description, tags_string]  
     fields:      
       file_path:  
         type:       admin_input_file_tag  
       tags_string:  
         name:       Tags  
         type:       input_tag

We need to define the type of input to use for the mock tags_string column because symfony does not have any data that it can use to determine what type of input it should use for tags (it was not defined in the schema.yml). The standard input_tag is used for this purpose. You can check that the photo module now handles tags properly by adding tags to the test photos. Just make sure you separate the tags with a blank space.

1566_fig8

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links