1<?php 2/* 3 * $Id$ 4 * 5 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 6 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 7 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 8 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 9 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 10 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 11 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 12 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 13 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 14 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 15 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 * 17 * This software consists of voluntary contributions made by many individuals 18 * and is licensed under the LGPL. For more information, see 19 * <http://www.doctrine-project.org>. 20 */ 21 22/** 23 * Doctrine_Record_Generator 24 * 25 * @author Konsta Vesterinen <kvesteri@cc.hut.fi> 26 * @package Doctrine 27 * @subpackage Plugin 28 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 29 * @version $Revision$ 30 * @link www.doctrine-project.org 31 * @since 1.0 32 */ 33abstract class Doctrine_Record_Generator extends Doctrine_Record_Abstract 34{ 35 /** 36 * _options 37 * 38 * @var array $_options an array of plugin specific options 39 */ 40 protected $_options = array( 41 'generateFiles' => false, 42 'generatePath' => false, 43 'builderOptions' => array(), 44 'identifier' => false, 45 'table' => false, 46 'pluginTable' => false, 47 'children' => array(), 48 'cascadeDelete' => true, 49 'appLevelDelete' => false 50 ); 51 52 /** 53 * Whether or not the generator has been initialized 54 * 55 * @var bool $_initialized 56 */ 57 protected $_initialized = false; 58 59 /** 60 * An alias for getOption 61 * 62 * @param string $option 63 */ 64 public function __get($option) 65 { 66 if (isset($this->_options[$option])) { 67 return $this->_options[$option]; 68 } 69 return null; 70 } 71 72 /** 73 * __isset 74 * 75 * @param string $option 76 */ 77 public function __isset($option) 78 { 79 return isset($this->_options[$option]); 80 } 81 82 /** 83 * Returns the value of an option 84 * 85 * @param $option the name of the option to retrieve 86 * @return mixed the value of the option 87 */ 88 public function getOption($name) 89 { 90 if ( ! isset($this->_options[$name])) { 91 throw new Doctrine_Exception('Unknown option ' . $name); 92 } 93 94 return $this->_options[$name]; 95 } 96 97 /** 98 * Sets given value to an option 99 * 100 * @param $option the name of the option to be changed 101 * @param $value the value of the option 102 * @return Doctrine_Plugin this object 103 */ 104 public function setOption($name, $value) 105 { 106 $this->_options[$name] = $value; 107 108 return $this; 109 } 110 111 /** 112 * Add child record generator 113 * 114 * @param Doctrine_Record_Generator $generator 115 * @return void 116 */ 117 public function addChild($generator) 118 { 119 $this->_options['children'][] = $generator; 120 } 121 122 /** 123 * Returns all options and their associated values 124 * 125 * @return array all options as an associative array 126 */ 127 public function getOptions() 128 { 129 return $this->_options; 130 } 131 132 /** 133 * Initialize the plugin. Call in Doctrine_Template setTableDefinition() in order to initiate a generator in a template 134 * 135 * @see Doctrine_Template_I18n 136 * @param Doctrine_Table $table 137 * @return void 138 */ 139 public function initialize(Doctrine_Table $table) 140 { 141 if ($this->_initialized) { 142 return false; 143 } 144 145 $this->_initialized = true; 146 147 $this->initOptions(); 148 149 $table->addGenerator($this, get_class($this)); 150 151 $this->_options['table'] = $table; 152 153 $ownerClassName = $this->_options['table']->getComponentName(); 154 $className = $this->_options['className']; 155 $this->_options['className'] = str_replace('%CLASS%', $ownerClassName, $className); 156 157 if (isset($this->_options['tableName'])) { 158 $ownerTableName = $this->_options['table']->getTableName(); 159 $tableName = $this->_options['tableName']; 160 $this->_options['tableName'] = str_replace('%TABLE%', $ownerTableName, $tableName); 161 } 162 163 // check that class doesn't exist (otherwise we cannot create it) 164 if ($this->_options['generateFiles'] === false && class_exists($this->_options['className'])) { 165 $this->_table = Doctrine_Core::getTable($this->_options['className']); 166 return false; 167 } 168 169 $this->buildTable(); 170 171 $fk = $this->buildForeignKeys($this->_options['table']); 172 173 $this->_table->setColumns($fk); 174 175 $this->buildRelation(); 176 177 $this->setTableDefinition(); 178 $this->setUp(); 179 180 $this->generateClassFromTable($this->_table); 181 182 $this->buildChildDefinitions(); 183 184 $this->_table->initIdentifier(); 185 } 186 187 /** 188 * Create the new Doctrine_Table instance in $this->_table based on the owning 189 * table. 190 * 191 * @return void 192 */ 193 public function buildTable() 194 { 195 // Bind model 196 $conn = $this->_options['table']->getConnection(); 197 $bindConnName = $conn->getManager()->getConnectionForComponent($this->_options['table']->getComponentName())->getName(); 198 if ($bindConnName) { 199 $conn->getManager()->bindComponent($this->_options['className'], $bindConnName); 200 } else { 201 $conn->getManager()->bindComponent($this->_options['className'], $conn->getName()); 202 } 203 204 // Create table 205 $tableClass = $conn->getAttribute(Doctrine_Core::ATTR_TABLE_CLASS); 206 $this->_table = new $tableClass($this->_options['className'], $conn); 207 $this->_table->setGenerator($this); 208 209 // If custom table name set then lets use it 210 if (isset($this->_options['tableName']) && $this->_options['tableName']) { 211 $this->_table->setTableName($this->_options['tableName']); 212 } 213 214 // Maintain some options from the parent table 215 $options = $this->_options['table']->getOptions(); 216 217 $newOptions = array(); 218 $maintain = array('type', 'collate', 'charset'); // This list may need updating 219 foreach ($maintain as $key) { 220 if (isset($options[$key])) { 221 $newOptions[$key] = $options[$key]; 222 } 223 } 224 225 $this->_table->setOptions($newOptions); 226 227 $conn->addTable($this->_table); 228 } 229 230 /** 231 * Empty template method for providing the concrete plugins the ability 232 * to initialize options before the actual definition is being built 233 * 234 * @return void 235 */ 236 public function initOptions() 237 { 238 239 } 240 241 /** 242 * Build the child behavior definitions that are attached to this generator 243 * 244 * @return void 245 */ 246 public function buildChildDefinitions() 247 { 248 if ( ! isset($this->_options['children'])) { 249 throw new Doctrine_Record_Exception("Unknown option 'children'."); 250 } 251 252 foreach ($this->_options['children'] as $child) { 253 if ($child instanceof Doctrine_Template) { 254 if ($child->getPlugin() !== null) { 255 $this->_table->addGenerator($child->getPlugin(), get_class($child->getPlugin())); 256 } 257 258 $this->_table->addTemplate(get_class($child), $child); 259 260 $child->setInvoker($this); 261 $child->setTable($this->_table); 262 $child->setTableDefinition(); 263 $child->setUp(); 264 } else { 265 $this->_table->addGenerator($child, get_class($child)); 266 $child->initialize($this->_table); 267 } 268 } 269 } 270 271 /** 272 * Generates foreign keys for the plugin table based on the owner table. 273 * These columns are automatically added to the generated model so we can 274 * create foreign keys back to the table object that owns the plugin. 275 * 276 * @param Doctrine_Table $table the table object that owns the plugin 277 * @return array an array of foreign key definitions 278 */ 279 public function buildForeignKeys(Doctrine_Table $table) 280 { 281 $fk = array(); 282 283 foreach ((array) $table->getIdentifier() as $field) { 284 $def = $table->getDefinitionOf($field); 285 286 unset($def['autoincrement']); 287 unset($def['sequence']); 288 unset($def['primary']); 289 290 $col = $table->hasColumn($field) ? $field : $table->getColumnName($field) . ' as ' . $field; 291 292 $def['primary'] = true; 293 $fk[$col] = $def; 294 } 295 return $fk; 296 } 297 298 /** 299 * Build the local relationship on the generated model for this generator 300 * instance which points to the invoking table in $this->_options['table'] 301 * 302 * @param string $alias Alias of the foreign relation 303 * @return void 304 */ 305 public function buildLocalRelation($alias = null) 306 { 307 $options = array( 308 'local' => $this->getRelationLocalKey(), 309 'foreign' => $this->getRelationForeignKey(), 310 'owningSide' => true 311 ); 312 313 if (isset($this->_options['cascadeDelete']) && $this->_options['cascadeDelete'] && ! $this->_options['appLevelDelete']) { 314 $options['onDelete'] = 'CASCADE'; 315 $options['onUpdate'] = 'CASCADE'; 316 } 317 318 $aliasStr = ''; 319 320 if ($alias !== null) { 321 $aliasStr = ' as ' . $alias; 322 } 323 324 $this->hasOne($this->_options['table']->getComponentName() . $aliasStr, $options); 325 } 326 327 /** 328 * Add a Doctrine_Relation::MANY relationship to the generator owner table 329 * 330 * @param string $name 331 * @param array $options 332 * @return void 333 */ 334 public function ownerHasMany($name, $options) 335 { 336 $this->_options['table']->hasMany($name, $options); 337 } 338 339 /** 340 * Add a Doctrine_Relation::ONE relationship to the generator owner table 341 * 342 * @param string $name 343 * @param array $options 344 * @return void 345 */ 346 public function ownerHasOne($name, $options) 347 { 348 $this->_options['table']->hasOne($name, $options); 349 } 350 351 /** 352 * Build the foreign relationship on the invoking table in $this->_options['table'] 353 * which points back to the model generated in this generator instance. 354 * 355 * @param string $alias Alias of the foreign relation 356 * @return void 357 */ 358 public function buildForeignRelation($alias = null) 359 { 360 $options = array( 361 'local' => $this->getRelationForeignKey(), 362 'foreign' => $this->getRelationLocalKey(), 363 'localKey' => false 364 ); 365 366 if (isset($this->_options['cascadeDelete']) && $this->_options['cascadeDelete'] && $this->_options['appLevelDelete']) { 367 $options['cascade'] = array('delete'); 368 } 369 370 $aliasStr = ''; 371 372 if ($alias !== null) { 373 $aliasStr = ' as ' . $alias; 374 } 375 376 $this->ownerHasMany($this->_table->getComponentName() . $aliasStr, $options); 377 } 378 379 /** 380 * Get the local key of the generated relationship 381 * 382 * @return string $local 383 */ 384 public function getRelationLocalKey() 385 { 386 return $this->getRelationForeignKey(); 387 } 388 389 /** 390 * Get the foreign key of the generated relationship 391 * 392 * @return string $foreign 393 */ 394 public function getRelationForeignKey() 395 { 396 $table = $this->_options['table']; 397 $identifier = $table->getIdentifier(); 398 399 foreach ((array) $identifier as $column) { 400 $def = $table->getDefinitionOf($column); 401 if (isset($def['primary']) && $def['primary'] && isset($def['autoincrement']) && $def['autoincrement']) { 402 return $column; 403 } 404 } 405 406 return $identifier; 407 } 408 409 /** 410 * This method can be used for generating the relation from the plugin 411 * table to the owner table. By default buildForeignRelation() and buildLocalRelation() are called 412 * Those methods can be overridden or this entire method can be overridden 413 * 414 * @return void 415 */ 416 public function buildRelation() 417 { 418 $this->buildForeignRelation(); 419 $this->buildLocalRelation(); 420 } 421 422 /** 423 * Generate a Doctrine_Record from a populated Doctrine_Table instance 424 * 425 * @param Doctrine_Table $table 426 * @return void 427 */ 428 public function generateClassFromTable(Doctrine_Table $table) 429 { 430 $definition = array(); 431 $definition['columns'] = $table->getColumns(); 432 $definition['tableName'] = $table->getTableName(); 433 $definition['actAs'] = $table->getTemplates(); 434 435 return $this->generateClass($definition); 436 } 437 438 /** 439 * Generates the class definition for plugin class 440 * 441 * @param array $definition Definition array defining columns, relations and options 442 * for the model 443 * @return void 444 */ 445 public function generateClass(array $definition = array()) 446 { 447 $definition['className'] = $this->_options['className']; 448 $definition['toString'] = isset($this->_options['toString']) ? $this->_options['toString'] : false; 449 if (isset($this->_options['listeners'])) { 450 $definition['listeners'] = $this->_options['listeners']; 451 } 452 453 $builder = new Doctrine_Import_Builder(); 454 $builderOptions = isset($this->_options['builderOptions']) ? (array) $this->_options['builderOptions']:array(); 455 $builder->setOptions($builderOptions); 456 457 if ($this->_options['generateFiles']) { 458 if (isset($this->_options['generatePath']) && $this->_options['generatePath']) { 459 $builder->setTargetPath($this->_options['generatePath']); 460 $builder->buildRecord($definition); 461 } else { 462 throw new Doctrine_Record_Exception('If you wish to generate files then you must specify the path to generate the files in.'); 463 } 464 } else { 465 $def = $builder->buildDefinition($definition); 466 467 eval($def); 468 } 469 } 470}