09-Inheritance.txt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. = Inheritance =
  2. [[PageOutline]]
  3. Developers often need one model table to extend another model table. Inheritance being an object-oriented notion, it doesn't have a true equivalent in the database world, so this is something an ORM must emulate. Propel offers two types of table inheritance: [http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html Single Table Inheritance], which is the most efficient implementations from a SQL and query performance perspective, but is limited to a small number of inherited fields ; and [http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html Concrete Table Inheritance], which provides the most features but adds a small overhead on write queries.
  4. == Single Table Inheritance ==
  5. In this implementation, one table is used for all subclasses. This has the implication that your table must have all columns needed by the main class and subclasses. Propel will create stub subclasses.
  6. Let's illustrate this idea with an example. Consider an object model with three classes, `Book`, `Essay`, and `Comic` - the first class being parent of the other two. With single table inheritance, the data of all three classes is stored in one table, named `book`.
  7. === Schema Definition ===
  8. A table using Single Table Inheritance requires a column to identify which class should be used to represent the ''table'' row. Classically, this column is named `class_key` - but you can choose whatever name fits your taste. The column needs the `inheritance="single"` attribute to make Propel understand that it's the class key column. Note that this 'key' column must be a real column in the table.
  9. {{{
  10. #!xml
  11. <table name="book">
  12. <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  13. <column name="title" type="VARCHAR" size="100"/>
  14. <column name="class_key" type="INTEGER" inheritance="single">
  15. <inheritance key="1" class="Book"/>
  16. <inheritance key="1" class="Essay" extends="Book"/>
  17. <inheritance key="2" class="Comic" extends="Book"/>
  18. </column>
  19. </table>
  20. }}}
  21. Once you rebuild your model, Propel generated all three model classes (`Book`, `Essay`, and `Comic`) and three query classes (`BookQuery`, `EssayQuery`, and `ComicQuery`). The `Essay` and `Comic` classes extend the `Book` class, the `EssayQuery` and `ComicQuery` classes extend `BookQuery`.
  22. '''Tip''': An inherited class can extend another inherited class. That mean that you can add a `Manga` kind of book that extends `Comic` instead of `Book`.
  23. === Using Inherited Objects ===
  24. Use inherited objects just like you use regular Propel model objects:
  25. {{{
  26. #!php
  27. <?php
  28. $book = new Book();
  29. $book->setTitle('War And Peace');
  30. $book->save();
  31. $essay = new Essay();
  32. $essay->setTitle('On the Duty of Civil Disobedience');
  33. $essay->save();
  34. $comic = new Comic();
  35. $comic->setTitle('Little Nemo In Slumberland');
  36. $comic->save();
  37. }}}
  38. Inherited objects share the same properties and methods by default, but you can add your own logic to each of the generated classes.
  39. Behind the curtain, Propel sets the `class_key` column based on the model class. So the previous code stores the following rows in the database:
  40. {{{
  41. id | title | class_key
  42. ---|-----------------------------------|----------
  43. 1 | War And Peace | Book
  44. 2 | On the Duty of Civil Disobedience | Essay
  45. 3 | Little Nemo In Slumberland | Comic
  46. }}}
  47. Incidentally, that means that you can add new classes manually, even if they are not defined as `<inheritance>` tags in the `schema.xml`:
  48. {{{
  49. #!php
  50. <?php
  51. class Novel extends Book
  52. {
  53. public function __construct()
  54. {
  55. parent::__construct();
  56. $this->setClassKey('Novel');
  57. }
  58. }
  59. $novel = new Novel();
  60. $novel->setTitle('Harry Potter');
  61. $novel->save();
  62. }}}
  63. === Retrieving Inherited objects ===
  64. In order to retrieve books, use the Query object of the main class, as you would usually do. Propel will hydrate children objects instead of the parent object when necessary:
  65. {{{
  66. #!php
  67. <?php
  68. $books = BookQuery::create()->find();
  69. foreach ($books as $book) {
  70. echo get_class($book) . ': ' . $book->getTitle() . "\n";
  71. }
  72. // Book: War And Peace
  73. // Essay: On the Duty of Civil Disobedience
  74. // Comic: Little Nemo In Slumberland
  75. // Novel: Harry Potter
  76. }}}
  77. If you want to retrieve only objects of a certain class, use the inherited query classes:
  78. {{{
  79. #!php
  80. <?php
  81. $comic = ComicQuery::create()
  82. ->findOne();
  83. echo get_class($comic) . ': ' . $comic->getTitle() . "\n";
  84. // Comic: Little Nemo In Slumberland
  85. }}}
  86. '''Tip''': You can override the base peer's `getOMClass()` to return the classname to use based on more complex logic (or query).
  87. === Abstract Entities ===
  88. If you wish to enforce using subclasses of an entity, you may declare a table "abstract" in your XML data model:
  89. {{{
  90. #!xml
  91. <table name="book" abstract="true">
  92. ...
  93. }}}
  94. That way users will only be able to instanciate `Essay` or `Comic` books, but not `Book`.
  95. == Concrete Table Inheritance ==
  96. Concrete Table Inheritance uses one table for each class in the hierarchy. Each table contains columns for the class and all its ancestors, so any fields in a superclass are duplicated across the tables of the subclasses.
  97. Propel implements Concrete Table Inheritance through a behavior.
  98. === Schema Definition ===
  99. Once again, this is easier to understand through an example. In a Content Management System, content types are often organized in a hierarchy, each subclass adding more fields to the superclass. So let's consider the following schema, where the `article` and `video` tables use the same fields as the main `content` tables, plus additional fields:
  100. {{{
  101. #!xml
  102. <table name="content">
  103. <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  104. <column name="title" type="VARCHAR" size="100"/>
  105. <column name="category_id" required="false" type="INTEGER" />
  106. <foreign-key foreignTable="category" onDelete="cascade">
  107. <reference local="category_id" foreign="id" />
  108. </foreign-key>
  109. </table>
  110. <table name="category">
  111. <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  112. <column name="name" type="VARCHAR" size="100" primaryString="true" />
  113. </table>
  114. <table name="article">
  115. <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  116. <column name="title" type="VARCHAR" size="100"/>
  117. <column name="body" type="VARCHAR" size="100"/>
  118. <column name="category_id" required="false" type="INTEGER" />
  119. <foreign-key foreignTable="category" onDelete="cascade">
  120. <reference local="category_id" foreign="id" />
  121. </foreign-key>
  122. </table>
  123. <table name="video">
  124. <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  125. <column name="title" type="VARCHAR" size="100"/>
  126. <column name="resource_link" type="VARCHAR" size="100"/>
  127. <column name="category_id" required="false" type="INTEGER" />
  128. <foreign-key foreignTable="category" onDelete="cascade">
  129. <reference local="category_id" foreign="id" />
  130. </foreign-key>
  131. </table>
  132. }}}
  133. Since the columns of the main table are copied to the child tables, this schema is a simple implementation of Concrete Table Inheritance. This is something that you can write by hand, but the repetition makes it tedious. Instead, you should let the `concrete_inheritance` behavior do it for you:
  134. {{{
  135. #!xml
  136. <table name="content">
  137. <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  138. <column name="title" type="VARCHAR" size="100"/>
  139. <column name="category_id" required="false" type="INTEGER" />
  140. <foreign-key foreignTable="category" onDelete="cascade">
  141. <reference local="category_id" foreign="id" />
  142. </foreign-key>
  143. </table>
  144. <table name="category">
  145. <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  146. <column name="name" type="VARCHAR" size="100" primaryString="true" />
  147. </table>
  148. <table name="article">
  149. <behavior name="concrete_inheritance">
  150. <parameter name="extends" value="content" />
  151. </behavior>
  152. <column name="body" type="VARCHAR" size="100"/>
  153. </table>
  154. <table name="video">
  155. <behavior name="concrete_inheritance">
  156. <parameter name="extends" value="content" />
  157. </behavior>
  158. <column name="resource_link" type="VARCHAR" size="100"/>
  159. </table>
  160. }}}
  161. '''Tip''': The `concrete_inheritance` behavior copies columns, foreign keys, indices and validators.
  162. === Using Inherited Model Classes ===
  163. For each of the tables in the schema above, Propel generates a Model class:
  164. {{{
  165. #!php
  166. <?php
  167. // create a new Category
  168. $cat = new Category();
  169. $cat->setName('Movie');
  170. $cat->save();
  171. // create a new Article
  172. $art = new Article();
  173. $art->setTitle('Avatar Makes Best Opening Weekend in the History');
  174. $art->setCategory($cat);
  175. $art->setContent('With $232.2 million worldwide total, Avatar had one of the best-opening weekends in the history of cinema.');
  176. $art->save();
  177. // create a new Video
  178. $vid = new Video();
  179. $vid->setTitle('Avatar Trailer');
  180. $vid->setCategory($cat);
  181. $vid->setResourceLink('http://www.avatarmovie.com/index.html')
  182. $vid->save();
  183. }}}
  184. And since the `concrete_inheritance` behavior tag defines a parent table, the `Article` and `Video` classes extend the `Content` class (same for the generated Query classes):
  185. {{{
  186. #!php
  187. <?php
  188. // methods of the parent model are accessible to the child models
  189. class Content extends BaseContent
  190. {
  191. public function getCategoryName()
  192. {
  193. return $this->getCategory()->getName();
  194. }
  195. }
  196. echo $art->getCategoryName(); // 'Movie'
  197. echo $vid->getCategoryName(); // 'Movie'
  198. // methods of the parent query are accessible to the child query
  199. class ContentQuery extends BaseContentQuery
  200. {
  201. public function filterByCategoryName($name)
  202. {
  203. return $this
  204. ->useCategoryQuery()
  205. ->filterByName($name)
  206. ->endUse();
  207. }
  208. }
  209. $articles = ArticleQuery::create()
  210. ->filterByCategoryName('Movie')
  211. ->find();
  212. }}}
  213. That makes of Concrete Table Inheritance a powerful way to organize your model logic and to avoid repetition, both in the schema and in the model code.
  214. === Data Replication ===
  215. By default, every time you save an `Article` or a `Video` object, Propel saves a copy of the `title` and `category_id` columns in a `Content` object. Consequently, retrieving objects regardless of their child type becomes very easy:
  216. {{{
  217. #!php
  218. <?php
  219. $conts = ContentQuery::create()->find();
  220. foreach ($conts as $content) {
  221. echo $content->getTitle() . "(". $content->getCategoryName() ")/n";
  222. }
  223. // Avatar Makes Best Opening Weekend in the History (Movie)
  224. // Avatar Trailer (Movie)
  225. }}}
  226. Propel also creates a one-to-one relationship between a object and its parent copy. That's why the schema definition above doesn't define any primary key for the `article` and `video` tables: the `concrete_inheritance` behavior creates the `id` primary key which is also a foreign key to the parent `id` column. So once you have a parent object, getting the child object is just one method call away:
  227. {{{
  228. #!php
  229. <?php
  230. class Article extends BaseArticle
  231. {
  232. public function getPreview()
  233. {
  234. return $this->getContent();
  235. }
  236. }
  237. class Movie extends BaseMovie
  238. {
  239. public function getPreview()
  240. {
  241. return $this->getResourceLink();
  242. }
  243. }
  244. $conts = ContentQuery::create()->find();
  245. foreach ($conts as $content) {
  246. echo $content->getTitle() . "(". $content->getCategoryName() ")/n"
  247. if ($content->hasChildObject()) {
  248. echo ' ' . $content->getChildObject()->getPreview(), "\n";
  249. }
  250. // Avatar Makes Best Opening Weekend in the History (Movie)
  251. // With $232.2 million worldwide total, Avatar had one of the best-opening
  252. // weekends in the history of cinema.
  253. // Avatar Trailer (Movie)
  254. // http://www.avatarmovie.com/index.html
  255. }}}
  256. The `hasChildObject()` and `getChildObject()` methods are automatically added by the behavior to the parent class. Behind the curtain, the saved `content` row has an additional `descendant_column` field allowing it to use the right model for the job.
  257. '''Tip''' You can disable the data replication by setting the `copy_data_to_parent` parameter to "false". In that case, the `concrete_inheritance` behavior simply modifies the table at buildtime and does nothing at runtime. Also, with `copy_data_to_parent` disabled, any primary key copied from the parent table is not turned into a foreign key:
  258. {{{
  259. #!xml
  260. <table name="article">
  261. <behavior name="concrete_inheritance">
  262. <parameter name="extends" value="content" />
  263. <parameter name="copy_data_to_parent" value="false" />
  264. </behavior>
  265. <column name="body" type="VARCHAR" size="100"/>
  266. </table>
  267. // results in
  268. <table name="article">
  269. <column name="body" type="VARCHAR" size="100"/>
  270. <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  271. <column name="title" type="VARCHAR" size="100"/>
  272. <column name="category_id" required="false" type="INTEGER" />
  273. <foreign-key foreignTable="category" onDelete="cascade">
  274. <reference local="category_id" foreign="id" />
  275. </foreign-key>
  276. </table>
  277. }}}