Export.php 25.5 KB
Newer Older
zYne's avatar
zYne committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.phpdoctrine.com>.
 */
zYne's avatar
zYne committed
21
Doctrine::autoload('Doctrine_Connection_Module');
zYne's avatar
zYne committed
22 23 24
/**
 * Doctrine_Export
 *
25 26
 * @package     Doctrine
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
zYne's avatar
zYne committed
27
 * @author      Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
28 29 30 31 32 33
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @category    Object Relational Mapping
 * @link        www.phpdoctrine.com
 * @since       1.0
 * @version     $Revision$
 */
lsmith's avatar
lsmith committed
34 35
class Doctrine_Export extends Doctrine_Connection_Module
{
36 37
    /**
     * drop an existing database
38
     * (this method is implemented by the drivers)
39 40 41 42
     *
     * @param string $name name of the database that should be dropped
     * @return void
     */
lsmith's avatar
lsmith committed
43 44
    public function dropDatabase($database)
    {
45 46
        throw new Doctrine_Export_Exception('Drop database not supported by this driver.');
    }
47 48
    /**
     * dropTable
zYne's avatar
zYne committed
49
     * drop an existing table
50
     *
zYne's avatar
zYne committed
51
     * @param string $table           name of table that should be dropped from the database
52 53 54
     * @throws PDOException
     * @return void
     */
lsmith's avatar
lsmith committed
55 56
    public function dropTable($table)
    {
zYne's avatar
zYne committed
57
        $this->conn->execute('DROP TABLE ' . $table);
58
    }
59 60 61 62

    /**
     * drop existing index
     *
zYne's avatar
zYne committed
63
     * @param string    $table        name of table that should be used in method
64 65 66
     * @param string    $name         name of the index to be dropped
     * @return void
     */
lsmith's avatar
lsmith committed
67 68
    public function dropIndex($table, $name)
    {
69
        $name = $this->conn->quoteIdentifier($this->conn->getIndexName($name), true);
zYne's avatar
zYne committed
70
        return $this->conn->exec('DROP INDEX ' . $name);
71 72 73 74 75 76 77 78 79
    }
    /**
     * drop existing constraint
     *
     * @param string    $table        name of table that should be used in method
     * @param string    $name         name of the constraint to be dropped
     * @param string    $primary      hint if the constraint is primary
     * @return void
     */
lsmith's avatar
lsmith committed
80 81
    public function dropConstraint($table, $name, $primary = false)
    {
82 83
        $table = $this->conn->quoteIdentifier($table, true);
        $name  = $this->conn->quoteIdentifier($this->conn->getIndexName($name), true);
zYne's avatar
zYne committed
84
        return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
85 86 87
    }
    /**
     * drop existing sequence
88
     * (this method is implemented by the drivers)
89 90 91 92
     *
     * @param string    $seq_name     name of the sequence to be dropped
     * @return void
     */
lsmith's avatar
lsmith committed
93 94
    public function dropSequence($name)
    {
95 96 97 98
        throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.');
    }
    /**
     * create a new database
99
     * (this method is implemented by the drivers)
100 101 102 103
     *
     * @param string $name name of the database that should be created
     * @return void
     */
lsmith's avatar
lsmith committed
104 105
    public function createDatabase($database)
    {
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
        throw new Doctrine_Export_Exception('Create database not supported by this driver.');
    }
    /**
     * create a new table
     *
     * @param string $name   Name of the database that should be created
     * @param array $fields  Associative array that contains the definition of each field of the new table
     *                       The indexes of the array entries are the names of the fields of the table an
     *                       the array entry values are associative arrays like those that are meant to be
     *                       passed with the field definitions to get[Type]Declaration() functions.
     *                          array(
     *                              'id' => array(
     *                                  'type' => 'integer',
     *                                  'unsigned' => 1
     *                                  'notnull' => 1
     *                                  'default' => 0
     *                              ),
     *                              'name' => array(
     *                                  'type' => 'text',
     *                                  'length' => 12
     *                              ),
     *                              'password' => array(
     *                                  'type' => 'text',
     *                                  'length' => 12
     *                              )
     *                          );
     * @param array $options  An associative array of table options:
     *
     * @return void
     */
zYne's avatar
zYne committed
136 137 138
    public function createTable($name, array $fields, array $options = array()) 
    {
        if ( ! $name) {
139
            throw new Doctrine_Export_Exception('no valid table name specified');
zYne's avatar
zYne committed
140 141
        }
        
lsmith's avatar
lsmith committed
142
        if (empty($fields)) {
143
            throw new Doctrine_Export_Exception('no fields specified for table '.$name);
lsmith's avatar
lsmith committed
144
        }
145
        $queryFields = $this->getFieldDeclarationList($fields);
146

147
        if (isset($options['primary']) && ! empty($options['primary'])) {
148
            $queryFields.= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')';
149 150
        }

151 152
        $name  = $this->conn->quoteIdentifier($name, true);
        $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')';
zYne's avatar
zYne committed
153

zYne's avatar
zYne committed
154
        return $this->conn->exec($query);
155 156 157
    }
    /**
     * create sequence
158
     * (this method is implemented by the drivers)
159
     *
zYne's avatar
zYne committed
160 161
     * @param string    $seqName        name of the sequence to be created
     * @param string    $start          start value of the sequence; default is 1
162 163
     * @return void
     */
zYne's avatar
zYne committed
164
    public function createSequence($seqName, $start = 1)
lsmith's avatar
lsmith committed
165
    {
166 167 168
        throw new Doctrine_Export_Exception('Create sequence not supported by this driver.');
    }

169
    /**
170 171 172 173 174 175 176 177 178 179
     * create a constraint on a table
     *
     * @param string    $table         name of the table on which the constraint is to be created
     * @param string    $name          name of the constraint to be created
     * @param array     $definition    associative array that defines properties of the constraint to be created.
     *                                 Currently, only one property named FIELDS is supported. This property
     *                                 is also an associative with the names of the constraint fields as array
     *                                 constraints. Each entry of this array is set to another type of associative
     *                                 array that specifies properties of the constraint that are specific to
     *                                 each field.
180
     *
181 182 183 184 185 186 187 188 189
     *                                 Example
     *                                    array(
     *                                        'fields' => array(
     *                                            'user_name' => array(),
     *                                            'last_login' => array()
     *                                        )
     *                                    )
     * @return void
     */
lsmith's avatar
lsmith committed
190 191
    public function createConstraint($table, $name, $definition)
    {
192 193
        $table = $this->conn->quoteIdentifier($table, true);
        $name = $this->conn->quoteIdentifier($this->conn->getIndexName($name), true);
194
        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
195
        if (!empty($definition['primary'])) {
196
            $query .= ' PRIMARY KEY';
197
        } elseif (!empty($definition['unique'])) {
198
            $query .= ' UNIQUE';
199 200 201
        }
        $fields = array();
        foreach (array_keys($definition['fields']) as $field) {
202
            $fields[] = $this->conn->quoteIdentifier($field, true);
203 204
        }
        $query .= ' ('. implode(', ', $fields) . ')';
zYne's avatar
zYne committed
205
        return $this->conn->exec($query);
206 207
    }
    /**
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
     * Get the stucture of a field into an array
     *
     * @param string    $table         name of the table on which the index is to be created
     * @param string    $name          name of the index to be created
     * @param array     $definition    associative array that defines properties of the index to be created.
     *                                 Currently, only one property named FIELDS is supported. This property
     *                                 is also an associative with the names of the index fields as array
     *                                 indexes. Each entry of this array is set to another type of associative
     *                                 array that specifies properties of the index that are specific to
     *                                 each field.
     *
     *                                 Currently, only the sorting property is supported. It should be used
     *                                 to define the sorting direction of the index. It may be set to either
     *                                 ascending or descending.
     *
     *                                 Not all DBMS support index sorting direction configuration. The DBMS
     *                                 drivers of those that do not support it ignore this property. Use the
     *                                 function supports() to determine whether the DBMS driver can manage indexes.
     *
     *                                 Example
     *                                    array(
     *                                        'fields' => array(
     *                                            'user_name' => array(
     *                                                'sorting' => 'ascending'
     *                                            ),
     *                                            'last_login' => array()
     *                                        )
     *                                    )
     * @return void
     */
lsmith's avatar
lsmith committed
238 239
    public function createIndex($table, $name, array $definition)
    {
zYne's avatar
zYne committed
240
        return $this->conn->execute($this->createIndexSql($table, $name, $definition));
241 242 243 244 245 246 247
    }
    /**
     * Get the stucture of a field into an array
     *
     * @param string    $table         name of the table on which the index is to be created
     * @param string    $name          name of the index to be created
     * @param array     $definition    associative array that defines properties of the index to be created.
248
     * @see Doctrine_Export::createIndex()
249 250
     * @return string
     */
lsmith's avatar
lsmith committed
251 252
    public function createIndexSql($table, $name, array $definition)
    {
253 254 255
        $table  = $this->conn->quoteIdentifier($table);
        $name   = $this->conn->quoteIdentifier($name);

zYne's avatar
zYne committed
256
        $query = 'CREATE INDEX ' . $name . ' ON ' . $table;
257 258 259 260
        $fields = array();
        foreach (array_keys($definition['fields']) as $field) {
            $fields[] = $this->conn->quoteIdentifier($field);
        }
zYne's avatar
zYne committed
261
        $query .= ' (' . implode(', ', $fields) . ')';
zYne's avatar
zYne committed
262

263
        return $query;
264 265 266 267 268 269 270 271 272 273 274
    }
    /**
     * createForeignKey
     *
     * @param string    $table         name of the table on which the index is to be created
     * @param string    $name          name of the foreign key to be created
     * @param array     $definition    associative array that defines properties of the foreign key to be created.
     */
    public function createForeignKey($table, $name, array $definition)
    {

275
    }
276 277
    /**
     * alter an existing table
278
     * (this method is implemented by the drivers)
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
     *
     * @param string $name         name of the table that is intended to be changed.
     * @param array $changes     associative array that contains the details of each type
     *                             of change that is intended to be performed. The types of
     *                             changes that are currently supported are defined as follows:
     *
     *                             name
     *
     *                                New name for the table.
     *
     *                            add
     *
     *                                Associative array with the names of fields to be added as
     *                                 indexes of the array. The value of each entry of the array
     *                                 should be set to another associative array with the properties
     *                                 of the fields to be added. The properties of the fields should
     *                                 be the same as defined by the MDB2 parser.
     *
     *
     *                            remove
     *
     *                                Associative array with the names of fields to be removed as indexes
     *                                 of the array. Currently the values assigned to each entry are ignored.
     *                                 An empty array should be used for future compatibility.
     *
     *                            rename
     *
     *                                Associative array with the names of fields to be renamed as indexes
     *                                 of the array. The value of each entry of the array should be set to
     *                                 another associative array with the entry named name with the new
     *                                 field name and the entry named Declaration that is expected to contain
     *                                 the portion of the field declaration already in DBMS specific SQL code
     *                                 as it is used in the CREATE TABLE statement.
     *
     *                            change
     *
     *                                Associative array with the names of the fields to be changed as indexes
     *                                 of the array. Keep in mind that if it is intended to change either the
     *                                 name of a field and any other properties, the change array entries
     *                                 should have the new names of the fields as array indexes.
     *
     *                                The value of each entry of the array should be set to another associative
     *                                 array with the properties of the fields to that are meant to be changed as
     *                                 array entries. These entries should be assigned to the new values of the
     *                                 respective properties. The properties of the fields should be the same
     *                                 as defined by the MDB2 parser.
     *
     *                            Example
     *                                array(
     *                                    'name' => 'userlist',
     *                                    'add' => array(
     *                                        'quota' => array(
     *                                            'type' => 'integer',
     *                                            'unsigned' => 1
     *                                        )
     *                                    ),
     *                                    'remove' => array(
     *                                        'file_limit' => array(),
     *                                        'time_limit' => array()
     *                                    ),
     *                                    'change' => array(
     *                                        'name' => array(
     *                                            'length' => '20',
     *                                            'definition' => array(
     *                                                'type' => 'text',
     *                                                'length' => 20,
     *                                            ),
     *                                        )
     *                                    ),
     *                                    'rename' => array(
     *                                        'sex' => array(
     *                                            'name' => 'gender',
     *                                            'definition' => array(
     *                                                'type' => 'text',
     *                                                'length' => 1,
     *                                                'default' => 'M',
     *                                            ),
     *                                        )
     *                                    )
     *                                )
     *
     * @param boolean $check     indicates whether the function should just check if the DBMS driver
     *                             can perform the requested table alterations if the value is true or
     *                             actually perform them otherwise.
     * @return void
     */
lsmith's avatar
lsmith committed
365 366
    public function alterTable($name, array $changes, $check)
    {
zYne's avatar
zYne committed
367
        $this->conn->execute($this->alterTableSql($name, $changes, $check));
368 369
    }
    /**
zYne's avatar
zYne committed
370
     * generates the sql for altering an existing table
371 372
     * (this method is implemented by the drivers)
     *
zYne's avatar
zYne committed
373 374 375 376 377 378
     * @param string $name          name of the table that is intended to be changed.
     * @param array $changes        associative array that contains the details of each type      *
     * @param boolean $check        indicates whether the function should just check if the DBMS driver
     *                              can perform the requested table alterations if the value is true or
     *                              actually perform them otherwise.
     * @see Doctrine_Export::alterTable()
379 380
     * @return string
     */
lsmith's avatar
lsmith committed
381 382
    public function alterTableSql($name, array $changes, $check)
    {
383 384 385 386 387
        throw new Doctrine_Export_Exception('Alter table not supported by this driver.');
    }
    /**
     * Get declaration of a number of field in bulk
     *
zYne's avatar
zYne committed
388
     * @param array $fields  a multidimensional associative array.
389 390 391 392 393
     *      The first dimension determines the field name, while the second
     *      dimension is keyed with the name of the properties
     *      of the field being declared as array indexes. Currently, the types
     *      of supported field properties are as follows:
     *
zYne's avatar
zYne committed
394 395 396 397 398
     *      length
     *          Integer value that determines the maximum length of the text
     *          field. If this argument is missing the field should be
     *          declared to have the longest length allowed by the DBMS.
     *
399
     *      default
zYne's avatar
zYne committed
400
     *          Text value to be used as default for this field.
401 402 403 404
     *
     *      notnull
     *          Boolean flag that indicates whether this field is constrained
     *          to not be set to null.
zYne's avatar
zYne committed
405 406 407 408
     *      charset
     *          Text value with the default CHARACTER SET for this field.
     *      collation
     *          Text value with the default COLLATION for this field.
409 410 411
     *
     * @return string
     */
lsmith's avatar
lsmith committed
412 413
    public function getFieldDeclarationList(array $fields)
    {
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
        foreach ($fields as $fieldName => $field) {
            $query = $this->getDeclaration($fieldName, $field);

            $queryFields[] = $query;
        }
        return implode(', ', $queryFields);
    }
    /**
     * Obtain DBMS specific SQL code portion needed to declare a generic type
     * field to be used in statements like CREATE TABLE.
     *
     * @param string $name   name the field to be declared.
     * @param array  $field  associative array with the name of the properties
     *      of the field being declared as array indexes. Currently, the types
     *      of supported field properties are as follows:
     *
     *      length
     *          Integer value that determines the maximum length of the text
     *          field. If this argument is missing the field should be
     *          declared to have the longest length allowed by the DBMS.
     *
     *      default
     *          Text value to be used as default for this field.
     *
     *      notnull
     *          Boolean flag that indicates whether this field is constrained
     *          to not be set to null.
     *      charset
     *          Text value with the default CHARACTER SET for this field.
     *      collation
     *          Text value with the default COLLATION for this field.
     * @return string  DBMS specific SQL code portion that should be used to
     *      declare the specified field.
     */
lsmith's avatar
lsmith committed
448 449
    public function getDeclaration($name, array $field)
    {
450 451

        $default = '';
lsmith's avatar
lsmith committed
452 453
        if (isset($field['default'])) {
            if ($field['default'] === '') {
454 455 456
                $field['default'] = empty($field['notnull'])
                    ? null : $this->valid_default_values[$field['type']];
                if ($field['default'] === ''
457
                    && ($conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)
458 459 460 461
                ) {
                    $field['default'] = ' ';
                }
            }
462 463

            $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
464
        }
465
        /**
lsmith's avatar
lsmith committed
466 467
        TODO: is this really needed for portability?
        elseif (empty($field['notnull'])) {
468 469 470 471 472 473
            $default = ' DEFAULT NULL';
        }
        */

        $charset = empty($field['charset']) ? '' :
            ' '.$this->getCharsetFieldDeclaration($field['charset']);
lsmith's avatar
lsmith committed
474

475 476
        $collation = empty($field['collation']) ? '' :
            ' '.$this->getCollationFieldDeclaration($field['collation']);
lsmith's avatar
lsmith committed
477

478 479 480 481
        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';

        $method = 'get' . $field['type'] . 'Declaration';

lsmith's avatar
lsmith committed
482
        if (method_exists($this->conn->dataDict, $method)) {
483
            return $this->conn->dataDict->$method($name, $field);
lsmith's avatar
lsmith committed
484
        } else {
485
            $dec = $this->conn->dataDict->getNativeDeclaration($field);
lsmith's avatar
lsmith committed
486
        }
487
        return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $collation;
488 489 490 491 492 493 494 495 496
    }
    /**
     * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @param string $charset   name of the charset
     * @return string  DBMS specific SQL code portion needed to set the CHARACTER SET
     *                 of a field declaration.
     */
lsmith's avatar
lsmith committed
497 498
    public function getCharsetFieldDeclaration($charset)
    {
499 500 501 502 503 504 505 506 507 508
        return '';
    }
    /**
     * Obtain DBMS specific SQL code portion needed to set the COLLATION
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @param string $collation   name of the collation
     * @return string  DBMS specific SQL code portion needed to set the COLLATION
     *                 of a field declaration.
     */
lsmith's avatar
lsmith committed
509 510
    public function getCollationFieldDeclaration($collation)
    {
511
        return '';
512
    }
513 514
    /**
     * export
zYne's avatar
zYne committed
515 516 517
     * method for exporting Doctrine_Record classes to a schema
     *
     * @return void
518
     */
lsmith's avatar
lsmith committed
519 520
    public static function exportAll()
    {
521 522 523 524 525
        $parent = new ReflectionClass('Doctrine_Record');
        $conn   = Doctrine_Manager::getInstance()->getCurrentConnection();
        $old    = $conn->getAttribute(Doctrine::ATTR_CREATE_TABLES);

        $conn->setAttribute(Doctrine::ATTR_CREATE_TABLES, true);
lsmith's avatar
lsmith committed
526 527

        foreach (get_declared_classes() as $name) {
528 529
            $class = new ReflectionClass($name);

lsmith's avatar
lsmith committed
530
            if ($class->isSubclassOf($parent) && ! $class->isAbstract()) {
531
                $obj = new $class();
lsmith's avatar
lsmith committed
532
            }
533 534 535
        }
        $conn->setAttribute(Doctrine::ATTR_CREATE_TABLES, $old);
    }
lsmith's avatar
lsmith committed
536 537
    public function export($record)
    {
lsmith's avatar
lsmith committed
538
        if ( ! $record instanceof Doctrine_Record)
zYne's avatar
zYne committed
539 540 541
            $record = new $record();

        $table = $record->getTable();
lsmith's avatar
lsmith committed
542

zYne's avatar
zYne committed
543 544
        $reporter = new Doctrine_Reporter();

lsmith's avatar
lsmith committed
545
        if ( ! Doctrine::isValidClassname($table->getComponentName())) {
546
            $reporter->add(E_WARNING, 'Badly named class.');
zYne's avatar
zYne committed
547
        }
lsmith's avatar
lsmith committed
548

zYne's avatar
zYne committed
549 550
        try {
            $columns = array();
lsmith's avatar
lsmith committed
551
            foreach ($table->getColumns() as $name => $column) {
zYne's avatar
zYne committed
552 553 554 555
                $definition = $column[2];
                $definition['type'] = $column[0];
                $definition['length'] = $column[1];

lsmith's avatar
lsmith committed
556
                if ($definition['type'] == 'enum' && isset($definition['default'])) {
zYne's avatar
zYne committed
557
                    $definition['default'] = $table->enumIndex($name, $definition['default']);
lsmith's avatar
lsmith committed
558 559
                }
                if ($definition['type'] == 'boolean' && isset($definition['default'])) {
zYne's avatar
zYne committed
560
                    $definition['default'] = (int) $definition['default'];
lsmith's avatar
lsmith committed
561
                }
zYne's avatar
zYne committed
562 563
                $columns[$name] = $definition;
            }
564

zYne's avatar
zYne committed
565 566 567
            $this->createTable($table->getTableName(), $columns);

        } catch(Doctrine_Connection_Exception $e) {
zYne's avatar
zYne committed
568
            $reporter->add(E_ERROR, $e->getMessage());
569
        }
zYne's avatar
zYne committed
570 571 572

        return $reporter;
    }
zYne's avatar
zYne committed
573
}