plugins.txt 9.33 KB
Newer Older
zYne's avatar
zYne committed
1
This chapter describes the usage of various plugins availible for Doctrine. You'll also learn how to create your own plugins. In order to grasp the concepts of this chapter you should already be familiar with the theory behind Doctrine_Template and Doctrine_Record_Generator. When refering to plugins we refer to class packages that use templates, generators and listeners extensively. All the introduced components in this chapter can be considered 'core' plugins, that means they reside at the Doctrine main repository. There are other official plugins too which can be found at the homesite of the Sensei project (www.sensei-project.org).
zYne's avatar
zYne committed
2

zYne's avatar
zYne committed
3
Usually plugins use generators side-to-side with template classes (classes that extend Doctrine_Template). The common workflow is:
zYne's avatar
zYne committed
4 5

# A new template is being initiliazed
zYne's avatar
zYne committed
6
# The template creates the generator and calls initialize() method
zYne's avatar
zYne committed
7 8
# The template is attached to given class

zYne's avatar
zYne committed
9
As you may already know templates are used for adding common definitions and options to record classes. The purpose of generators is much more complex. Usually they are being used for creating generic record classes dynamically. The definitions of these generic classes usually depend on the owner class. For example the columns of the auditlog versioning class are the columns of the parent class with all the sequence and autoincrement definitions removed.
zYne's avatar
zYne committed
10 11


zYne's avatar
zYne committed
12 13
++ Internationalization with I18n

zYne's avatar
zYne committed
14
Doctrine_I18n package is a plugin for Doctrine that provides internationalization support for record classes. In the following example we have a NewsItem class with two fields 'title' and 'content'. We want to have the field 'title' with different languages support. This can be achieved as follows:
zYne's avatar
zYne committed
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

<code type="php">
class NewsItem extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('title', 'string', 200);
        $this->hasColumn('content', 'string');
    }

    public function setUp()
    {
        $this->actAs('I18n', array('fields' => array('title')));
    }
}
</code>
zYne's avatar
zYne committed
31 32 33 34 35 36

Now the first time you initialize a new NewsItem record Doctrine initializes the plugin that builds the followings things:

1. Record class called NewsItemTranslation
2. Bi-directional relations between NewsItemTranslation and NewsItem

zYne's avatar
zYne committed
37 38 39 40 41 42 43 44 45 46 47 48 49
+++ Creating the I18n table

The I18n table can be created as follows:

<code type="php">
$conn->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);

$conn->export->exportClasses(array('NewsItem'));
</code>

The following code example executes two sql statements. When using mysql those statements would look like:

<code>
zYne's avatar
zYne committed
50
CREATE TABLE news_item (id INT NOT NULL AUTO_INCREMENT, content TEXT)
zYne's avatar
zYne committed
51 52 53
CREATE TABLE news_item_translation (id INT NOT NULL, title VARCHAR(200), lang VARCHAR(20))
</code>

zYne's avatar
zYne committed
54 55
Notice how the field 'title' is not present in the news_item table. Since its present in the translation table it would be a waste of resources to have that same field in the main table. Basically Doctrine always automatically removes all translated fields from the main table.

zYne's avatar
zYne committed
56 57 58 59 60 61 62 63 64
+++ Using I18n

In the following example we add some data with finnish and english translations:

<code type="php">
$item = new NewsItem();
$item->content = 'This is some content. This field is not being translated.';

$item->Translation['FI']->title = 'Joku otsikko';
Jonathan.Wage's avatar
Jonathan.Wage committed
65
$item->Translation['EN']->title = 'Some title';
zYne's avatar
zYne committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
$item->save();
</code>

Now lets find all items and their finnish translations:

<code type="php">
$items = Doctrine_Query::create()
         ->from('NewsItem n')
         ->leftJoin('n.Translation t INDEXBY t.lang')
         ->where('t.lang = ?')
         ->execute(array('FI'));

$items[0]->Translation['FI']->title; // 'joku otsikko'
</code>


zYne's avatar
zYne committed
82
++ AuditLog and versioning
zYne's avatar
zYne committed
83
Doctrine_AuditLog provides a full versioning solution. Lets say we have a NewsItem class that we want to be versioned. This functionality can be applied by simply adding $this->actAs('Versionable') into your record setup. 
zYne's avatar
zYne committed
84

85
<code type="php">
zYne's avatar
zYne committed
86 87 88 89 90 91
class NewsItem extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('title', 'string', 200);
        $this->hasColumn('content', 'string');
zYne's avatar
zYne committed
92 93
        // the versioning plugin needs version column
        $this->hasColumn('version', 'integer');
zYne's avatar
zYne committed
94 95 96 97
    }

    public function setUp()
    {
zYne's avatar
zYne committed
98
        $this->actAs('Versionable');
zYne's avatar
zYne committed
99 100 101 102
    }
}
</code>

zYne's avatar
zYne committed
103 104 105 106 107 108
Now when we have defined this record to be versionable, Doctrine does internally the following things:

* It creates a class called NewsItemVersion on-the-fly, the table this record is pointing at is news_item_version
* Everytime a NewsItem object is deleted / updated the previous version is stored into news_item_version
* Everytime a NewsItem object is updated its version number is increased.

zYne's avatar
zYne committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
+++ Creating the version table

As with all other plugins, the plugin-table, in this case the table that holds the different versions, can be created by enabling Doctrine::EXPORT_PLUGINS. The easiest way to set this is by setting the value of Doctrine::ATTR_EXPORT to Doctrine::EXPORT_ALL. The following example shows the usage:

<code type="php">
$conn->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);

$conn->export->exportClasses(array('NewsItem'));
</code>

The following code example executes two sql statements. When using mysql those statements would look like:

<code>
CREATE TABLE news_item (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(200), content TEXT, version INTEGER)
CREATE TABLE news_item_version (id INT NOT NULL, title VARCHAR(200), content TEXT, version INTEGER)
</code>
zYne's avatar
zYne committed
125

zYne's avatar
zYne committed
126
+++ Using versioning
zYne's avatar
zYne committed
127

128
<code type="php">
zYne's avatar
zYne committed
129 130 131 132 133 134 135
$newsItem = new NewsItem();
$newsItem->title = 'No news is good news';
$newsItem->content = 'All quiet on the western front';

$newsItem->save();
$newsItem->version; // 1

zYne's avatar
zYne committed
136
$newsItem->title = 'A different title';
zYne's avatar
zYne committed
137 138 139 140
$newsItem->save();
$newsItem->version; // 2
</code>

zYne's avatar
zYne committed
141 142 143
+++ Reverting changes

Doctrine_Record provides a method called revert() which can be used for reverting to specified version. Internally Doctrine queries the version table and fetches the data for given version. If the given version is not found a Doctrine_Record_Exception is being thrown.
zYne's avatar
zYne committed
144

145
<code type="php">
zYne's avatar
zYne committed
146 147 148 149 150
$newsItem->revert(1);

$newsItem->title; // No news is good news
</code>

zYne's avatar
zYne committed
151 152 153 154
+++ Advanced usage

There are many options for the versioning plugin. Sometimes you may want to use other version column than 'version'. This can be achieved by giving the options parameter to actAs() method.

155
<code type="php">
zYne's avatar
zYne committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
class NewsItem extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('title', 'string', 200);
        $this->hasColumn('content', 'string');
        // the versioning plugin needs version column
        $this->hasColumn('news_version', 'integer');
    }

    public function setUp()
    {
        $this->actAs('Versionable', array('versionColumn' => 'news_version'));
    }
}
</code>

You can also control the name of the versioning record and the name of the version table with option attributes 'className' and 'tableName'.

zYne's avatar
zYne committed
175 176 177 178
++ Soft-delete

Soft-delete is a very simple plugin for achieving the following behaviour: when a record is deleted its not removed from database. Usually the record contains some special field like 'deleted' which tells the state of the record (deleted or alive).

179
The following code snippet shows what you need in order to achieve this kind of behaviour. Notice how we define two event hooks: preDelete and postDelete. Also notice how the preDelete hook skips the actual delete-operation with skipOperation() call. For more info about the event hooks see chapter [doc event-listeners :index :name].
zYne's avatar
zYne committed
180

181
<code type="php">
zYne's avatar
zYne committed
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
class SoftDeleteTest extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string', null, array('primary' => true));
        $this->hasColumn('deleted', 'boolean', 1);
    }
    public function preDelete($event)
    {
        $event->skipOperation();
    }
    public function postDelete($event)
    {
        $this->deleted = true;
        $this->save();
    }
}
</code>

Now lets put the plugin in action:

203
<code type="php">
zYne's avatar
zYne committed
204 205 206 207 208 209 210 211 212

// save a new record
$record = new SoftDeleteTest();
$record->name = 'new record';
$record->save();

$record->delete();
var_dump($record->deleted); // true
</code>
zYne's avatar
zYne committed
213 214 215

++ Creating plugins

zYne's avatar
zYne committed
216
This subchapter provides you the means for creating your own plugins. Lets say we have various different Record classes that need to have one-to-many emails. We achieve this functionality by creating a generic plugin which creates Email classes on the fly.
zYne's avatar
zYne committed
217

zYne's avatar
zYne committed
218
We start this task by creating a plugin called EmailPlugin with buildDefinition() method. Inside the buildDefinition() method various helper methods can be used for easily creating the dynamic record definition. Commonly the following methods are being used:
zYne's avatar
zYne committed
219

zYne's avatar
zYne committed
220
# buildForeignKeys()
zYne's avatar
zYne committed
221 222

<code type="php">
zYne's avatar
zYne committed
223
class EmailPlugin extends Doctrine_Record_Generator
zYne's avatar
zYne committed
224 225 226 227 228 229
{
    public function initOptions()
    {
        $this->setOption('className', '%CLASS%Email');
    }
    
zYne's avatar
zYne committed
230
    public function setTableDefinition()
zYne's avatar
zYne committed
231
    {
zYne's avatar
zYne committed
232 233
        $this->hasColumn('address', 'string', 255, array('email'  => true,
                                                         'primary' => true));
zYne's avatar
zYne committed
234 235 236 237 238 239
    }
}
</code>

++ Nesting plugins