Relational databases do not support inheritance. If we need to store inheritance in the database, then we should somehow support it through code. This code should be efficient, so it should generate as less JOINs as possible. A common solution to this problem was described by Martin Fowler and named single table inheritance.
When we use this pattern, we store all the class tree data in a single table and use the type field to determine a model for each row.
As an example, we will implement the single table inheritance for the following class tree:
Car
|- SportCar
|- FamilyCar
Getting ready
- Create a new application by using yiic webapp as described in the official guide
- Create and set up a database. Add the following table:
CREATE TABLE `car` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `type` varchar(100) NOT NULL, PRIMARY KEY (`id`) ); INSERT INTO `car` (`name`, `type`) VALUES ('Ford Focus', 'family'), ('Opel Astra', 'family'), ('Kia Ceed', 'family'), ('Porsche Boxster', 'sport'), ('Ferrari 550', 'sport');
How to do it...
- First, we will create the car model protected/models/Car.php as follows:
- Then, we implement protected/models/SportCar.php as follows:
- Also implement protected/models/FamilyCar.php as follows:
- Now create protected/controllers/TestController.php as follows:
- Run test/index and you should get the following output:
<?php class Car extends CActiveRecord { public static function model($className = __CLASS__) { return parent::model($className); } public function tableName() { return 'car'; } protected function instantiate($attributes) { switch ($attributes['type']) { case 'sport': $class = 'SportCar'; break; case 'family': $class = 'FamilyCar'; break; default: $class = get_class($this); } $model = new $class(null); return $model; } }
<?php class SportCar extends Car { public static function model($className = __CLASS__) { return parent::model($className); } public function defaultScope() { return array( 'condition' => "type='sport'", ); } }
<?php class FamilyCar extends Car { public static function model($className = __CLASS__) { return parent::model($className); } public function defaultScope() { return array( 'condition' => "type='family'", ); } }
<?php class TestController extends CController { public function actionIndex() { echo "All cars
"; $cars = Car::model()->findAll(); foreach ($cars as $car) { // Each car can be of class Car, SportCar or FamilyCar echo get_class($car) . ' ' . $car->name . "
"; } echo "Sport cars only
"; $sportCars = SportCar::model()->findAll(); foreach ($sportCars as $car) { // Each car should be SportCar echo get_class($car) . ' ' . $car->name . "
"; } } }
How it works...
The base model Car is a typically used Yii AR model except two added methods. tableName explicitly declares the table name to be used for the model. For the Car model alone, this does not make sense but for child models, it will return the same car table which is just what we want—a single table for the entire class tree. instantiate is used by AR internally to create a model instance from the raw data when we call methods, such as Car::model()->findAll(). We use a switch statement to create different classes based on the type attribute and use the same class if the attribute value is either not specified or points to the non-existing class. SportCar and FamilyCar models simply set the default AR scope, so when we find models with SportCar::model()-> methods, we will get the SportCar model only.There's more...
Use the following references to learn more about the single table inheritance pattern and YiiActive Record implementation:
- http://martinfowler.com/eaaCatalog/singleTableInheritance.html
- http://www.yiiframework.com/doc/api/CActiveRecord/
0 comments:
Post a Comment