1
0
mirror of synced 2024-12-05 03:06:05 +03:00

[2.0] Work on mapping drivers, exporter drivers and reverse engineering of database schemas

This commit is contained in:
jwage 2009-10-07 04:07:23 +00:00
parent c8362da494
commit 165abc3ca4
14 changed files with 452 additions and 329 deletions

View File

@ -72,198 +72,13 @@ class Inflector
}
/**
* Check if a string has utf7 characters in it
* Camelize a word. This uses the classify() method and turns the first character to lowercase
*
* By bmorel at ssi dot fr
*
* @param string $string
* @return boolean $bool
* @param string $word
* @return string $word
*/
public static function seemsUtf8($string)
public static function camelize($word)
{
for ($i = 0; $i < strlen($string); $i++) {
if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb
elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb
elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb
elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb
elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb
elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b
else return false; # Does not match any model
for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80))
return false;
}
}
return true;
}
/**
* Remove any illegal characters, accents, etc.
*
* @param string $string String to unaccent
* @return string $string Unaccented string
*/
public static function unaccent($string)
{
if ( ! preg_match('/[\x80-\xff]/', $string) ) {
return $string;
}
if (self::seemsUtf8($string)) {
$chars = array(
// Decompositions for Latin-1 Supplement
chr(195).chr(128) => 'A', chr(195).chr(129) => 'A',
chr(195).chr(130) => 'A', chr(195).chr(131) => 'A',
chr(195).chr(132) => 'A', chr(195).chr(133) => 'A',
chr(195).chr(135) => 'C', chr(195).chr(136) => 'E',
chr(195).chr(137) => 'E', chr(195).chr(138) => 'E',
chr(195).chr(139) => 'E', chr(195).chr(140) => 'I',
chr(195).chr(141) => 'I', chr(195).chr(142) => 'I',
chr(195).chr(143) => 'I', chr(195).chr(145) => 'N',
chr(195).chr(146) => 'O', chr(195).chr(147) => 'O',
chr(195).chr(148) => 'O', chr(195).chr(149) => 'O',
chr(195).chr(150) => 'O', chr(195).chr(153) => 'U',
chr(195).chr(154) => 'U', chr(195).chr(155) => 'U',
chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y',
chr(195).chr(159) => 's', chr(195).chr(160) => 'a',
chr(195).chr(161) => 'a', chr(195).chr(162) => 'a',
chr(195).chr(163) => 'a', chr(195).chr(164) => 'a',
chr(195).chr(165) => 'a', chr(195).chr(167) => 'c',
chr(195).chr(168) => 'e', chr(195).chr(169) => 'e',
chr(195).chr(170) => 'e', chr(195).chr(171) => 'e',
chr(195).chr(172) => 'i', chr(195).chr(173) => 'i',
chr(195).chr(174) => 'i', chr(195).chr(175) => 'i',
chr(195).chr(177) => 'n', chr(195).chr(178) => 'o',
chr(195).chr(179) => 'o', chr(195).chr(180) => 'o',
chr(195).chr(181) => 'o', chr(195).chr(182) => 'o',
chr(195).chr(182) => 'o', chr(195).chr(185) => 'u',
chr(195).chr(186) => 'u', chr(195).chr(187) => 'u',
chr(195).chr(188) => 'u', chr(195).chr(189) => 'y',
chr(195).chr(191) => 'y',
// Decompositions for Latin Extended-A
chr(196).chr(128) => 'A', chr(196).chr(129) => 'a',
chr(196).chr(130) => 'A', chr(196).chr(131) => 'a',
chr(196).chr(132) => 'A', chr(196).chr(133) => 'a',
chr(196).chr(134) => 'C', chr(196).chr(135) => 'c',
chr(196).chr(136) => 'C', chr(196).chr(137) => 'c',
chr(196).chr(138) => 'C', chr(196).chr(139) => 'c',
chr(196).chr(140) => 'C', chr(196).chr(141) => 'c',
chr(196).chr(142) => 'D', chr(196).chr(143) => 'd',
chr(196).chr(144) => 'D', chr(196).chr(145) => 'd',
chr(196).chr(146) => 'E', chr(196).chr(147) => 'e',
chr(196).chr(148) => 'E', chr(196).chr(149) => 'e',
chr(196).chr(150) => 'E', chr(196).chr(151) => 'e',
chr(196).chr(152) => 'E', chr(196).chr(153) => 'e',
chr(196).chr(154) => 'E', chr(196).chr(155) => 'e',
chr(196).chr(156) => 'G', chr(196).chr(157) => 'g',
chr(196).chr(158) => 'G', chr(196).chr(159) => 'g',
chr(196).chr(160) => 'G', chr(196).chr(161) => 'g',
chr(196).chr(162) => 'G', chr(196).chr(163) => 'g',
chr(196).chr(164) => 'H', chr(196).chr(165) => 'h',
chr(196).chr(166) => 'H', chr(196).chr(167) => 'h',
chr(196).chr(168) => 'I', chr(196).chr(169) => 'i',
chr(196).chr(170) => 'I', chr(196).chr(171) => 'i',
chr(196).chr(172) => 'I', chr(196).chr(173) => 'i',
chr(196).chr(174) => 'I', chr(196).chr(175) => 'i',
chr(196).chr(176) => 'I', chr(196).chr(177) => 'i',
chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij',
chr(196).chr(180) => 'J', chr(196).chr(181) => 'j',
chr(196).chr(182) => 'K', chr(196).chr(183) => 'k',
chr(196).chr(184) => 'k', chr(196).chr(185) => 'L',
chr(196).chr(186) => 'l', chr(196).chr(187) => 'L',
chr(196).chr(188) => 'l', chr(196).chr(189) => 'L',
chr(196).chr(190) => 'l', chr(196).chr(191) => 'L',
chr(197).chr(128) => 'l', chr(197).chr(129) => 'L',
chr(197).chr(130) => 'l', chr(197).chr(131) => 'N',
chr(197).chr(132) => 'n', chr(197).chr(133) => 'N',
chr(197).chr(134) => 'n', chr(197).chr(135) => 'N',
chr(197).chr(136) => 'n', chr(197).chr(137) => 'N',
chr(197).chr(138) => 'n', chr(197).chr(139) => 'N',
chr(197).chr(140) => 'O', chr(197).chr(141) => 'o',
chr(197).chr(142) => 'O', chr(197).chr(143) => 'o',
chr(197).chr(144) => 'O', chr(197).chr(145) => 'o',
chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe',
chr(197).chr(148) => 'R', chr(197).chr(149) => 'r',
chr(197).chr(150) => 'R', chr(197).chr(151) => 'r',
chr(197).chr(152) => 'R', chr(197).chr(153) => 'r',
chr(197).chr(154) => 'S', chr(197).chr(155) => 's',
chr(197).chr(156) => 'S', chr(197).chr(157) => 's',
chr(197).chr(158) => 'S', chr(197).chr(159) => 's',
chr(197).chr(160) => 'S', chr(197).chr(161) => 's',
chr(197).chr(162) => 'T', chr(197).chr(163) => 't',
chr(197).chr(164) => 'T', chr(197).chr(165) => 't',
chr(197).chr(166) => 'T', chr(197).chr(167) => 't',
chr(197).chr(168) => 'U', chr(197).chr(169) => 'u',
chr(197).chr(170) => 'U', chr(197).chr(171) => 'u',
chr(197).chr(172) => 'U', chr(197).chr(173) => 'u',
chr(197).chr(174) => 'U', chr(197).chr(175) => 'u',
chr(197).chr(176) => 'U', chr(197).chr(177) => 'u',
chr(197).chr(178) => 'U', chr(197).chr(179) => 'u',
chr(197).chr(180) => 'W', chr(197).chr(181) => 'w',
chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y',
chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z',
chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z',
chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z',
chr(197).chr(190) => 'z', chr(197).chr(191) => 's',
// Euro Sign
chr(226).chr(130).chr(172) => 'E',
// GBP (Pound) Sign
chr(194).chr(163) => '',
'Ä' => 'Ae', 'ä' => 'ae', 'Ü' => 'Ue', 'ü' => 'ue',
'Ö' => 'Oe', 'ö' => 'oe', 'ß' => 'ss');
$string = strtr($string, $chars);
} else {
// Assume ISO-8859-1 if not UTF-8
$chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158)
.chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194)
.chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202)
.chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210)
.chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218)
.chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227)
.chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235)
.chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243)
.chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251)
.chr(252).chr(253).chr(255);
$chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
$string = strtr($string, $chars['in'], $chars['out']);
$doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
$doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
$string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
}
return $string;
}
/**
* Convert any passed string to a url friendly string. Converts 'My first blog post' to 'my-first-blog-post'
*
* @param string $text Text to urlize
* @return string $text Urlized text
*/
public static function urlize($text)
{
// Remove all non url friendly characters with the unaccent function
$text = self::unaccent($text);
if (function_exists('mb_strtolower'))
{
$text = mb_strtolower($text);
} else {
$text = strtolower($text);
}
// Remove all none word characters
$text = preg_replace('/\W/', ' ', $text);
// More stripping. Replace spaces with dashes
$text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-',
preg_replace('/([a-z\d])([A-Z])/', '\1_\2',
preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2',
preg_replace('/::/', '/', $text)))));
return trim($text, '-');
return lcfirst(self::classify($word));
}
}

View File

@ -177,4 +177,10 @@ abstract class Type
self::$_typesMap[$name] = $className;
}
public function __toString()
{
$e = explode('\\', get_class($this));
return str_replace('Type', '', end($e));
}
}

View File

@ -0,0 +1,158 @@
<?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.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\DoctrineException,
Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\DBAL\Schema\AbstractSchemaManager,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException,
Doctrine\Common\Util\Inflector;
/**
* The DatabaseDriver reverse engineers the mapping metadata from a database
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class DatabaseDriver implements Driver
{
/** The SchemaManager. */
private $_sm;
/**
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
* docblock annotations.
*
* @param AnnotationReader $reader The AnnotationReader to use.
*/
public function __construct(AbstractSchemaManager $schemaManager)
{
$this->_sm = $schemaManager;
}
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$tableName = $className;
$className = Inflector::classify($tableName);
$metadata->name = $className;
$metadata->primaryTable['name'] = $tableName;
$columns = $this->_sm->listTableColumns($tableName);
$foreignKeys = $this->_sm->listTableForeignKeys($tableName);
$ids = array();
$fieldMappings = array();
foreach ($columns as $column) {
// Skip columns that are foreign keys
foreach ($foreignKeys as $foreignKey) {
if ($column['name'] == $foreignKey['local']) {
continue(2);
}
}
$fieldMapping = array();
if ($column['primary']) {
$fieldMapping['id'] = true;
}
$fieldMapping['fieldName'] = Inflector::camelize($column['name']);
$fieldMapping['columnName'] = $column['name'];
$fieldMapping['type'] = strtolower((string) $column['type']);
$fieldMapping['length'] = $column['length'];
$fieldMapping['unsigned'] = $column['unsigned'];
$fieldMapping['fixed'] = $column['fixed'];
$fieldMapping['notnull'] = $column['notnull'];
$fieldMapping['default'] = $column['default'];
if (isset($fieldMapping['id'])) {
$ids[] = $fieldMapping;
} else {
$fieldMappings[] = $fieldMapping;
}
}
if ($ids) {
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
foreach ($ids as $id) {
$metadata->mapField($id);
}
}
foreach ($fieldMappings as $fieldMapping) {
$metadata->mapField($fieldMapping);
}
foreach ($foreignKeys as $foreignKey) {
$associationMapping = array();
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', $foreignKey['local']));
$associationMapping['columnName'] = $foreignKey['local'];
$associationMapping['targetEntity'] = Inflector::classify($foreignKey['table']);
$associationMapping['joinColumns'][] = array(
'name' => $foreignKey['local'],
'referencedColumnName' => $foreignKey['foreign']
);
$metadata->mapManyToOne($associationMapping);
}
}
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
* MappedSuperclass.
*
* @param string $className
* @return boolean
*/
public function isTransient($className)
{
return true;
}
/**
* Preloads all mapping information found in any documents within the
* configured paths and returns a list of class names that have been preloaded.
*
* @return array The list of class names that have been preloaded.
*/
public function preload()
{
$tables = array();
foreach ($this->_sm->listTables() as $table) {
$tables[] = $table;
}
return $tables;
}
}

View File

@ -149,39 +149,49 @@ class YamlDriver extends AbstractFileDriver
// Evaluate fields
if (isset($element['fields'])) {
foreach ($element['fields'] as $name => $fieldMapping) {
$e = explode('(', $fieldMapping['type']);
$fieldMapping['type'] = $e[0];
if (isset($e[1])) {
$fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
}
$mapping = array(
'fieldName' => $name,
'type' => $fieldMapping['type']
);
if (isset($fieldMapping['id'])) {
$mapping['id'] = true;
if (isset($fieldMapping['generator']['strategy'])) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
. strtoupper($fieldMapping['generator']['strategy'])));
}
}
// Check for SequenceGenerator/TableGenerator definition
if (isset($fieldMapping['sequenceGenerator'])) {
$metadata->setSequenceGeneratorDefinition($fieldMapping['sequenceGenerator']);
} else if (isset($fieldMapping['tableGenerator'])) {
throw DoctrineException::tableIdGeneratorNotImplemented();
}
if (isset($fieldMapping['column'])) {
$mapping['columnName'] = $fieldMapping['column'];
}
if (isset($fieldMapping['length'])) {
$mapping['length'] = $fieldMapping['length'];
}
if (isset($fieldMapping['precision'])) {
$mapping['precision'] = $fieldMapping['precision'];
}
if (isset($fieldMapping['scale'])) {
$mapping['scale'] = $fieldMapping['scale'];
}
if (isset($fieldMapping['unique'])) {
$mapping['unique'] = (bool)$fieldMapping['unique'];
}
if (isset($fieldMapping['options'])) {
$mapping['options'] = $fieldMapping['options'];
}
if (isset($fieldMapping['notnull'])) {
$mapping['notnull'] = $fieldMapping['notnull'];
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$metadata->setVersionMapping($mapping);
}

View File

@ -101,6 +101,10 @@ class ConvertMappingTask extends AbstractTask
$printer->writeln('You can only use the --extend argument when converting to annoations.');
return false;
}
if ($args['from'][0] == 'database') {
$config = $this->_em->getConfiguration();
$config->setMetadataDriverImpl(new \Doctrine\ORM\Mapping\Driver\DatabaseDriver($this->_em->getConnection()->getSchemaManager()));
}
return true;
}
@ -127,47 +131,83 @@ class ConvertMappingTask extends AbstractTask
$printer->writeln('Converting Doctrine 1 schema to Doctrine 2 mapping files', 'INFO');
$converter = new \Doctrine\ORM\Tools\ConvertDoctrine1Schema($from);
$exporter->setMetadatas($converter->getMetadatasFromSchema());
$metadatas = $converter->getMetadatasFromSchema();
} else {
foreach ($from as $path) {
$type = $this->_determinePathType($path);
foreach ($from as $source) {
$sourceArg = $source;
$printer->writeln(sprintf('Adding %s mapping directory: "%s"', $type, $path), 'INFO');
$type = $this->_determineSourceType($sourceArg);
$source = $this->_getSourceByType($type, $sourceArg);
$cme->addMappingDir($path, $type);
$printer->writeln(sprintf('Adding "%s" mapping source', $sourceArg), 'INFO');
$cme->addMappingSource($source, $type);
}
$exporter->setMetadatas($cme->getMetadatasForMappingDirectories());
$metadatas = $cme->getMetadatasForMappingSources();
}
$printer->writeln(sprintf('Exporting %s mapping information to directory: "%s"', $args['to'], $args['dest']), 'INFO');
foreach ($metadatas as $metadata) {
$printer->write('Processing entity "')
->write($metadata->name, 'KEYWORD')->writeln('"');
}
$printer->writeln(sprintf('Exporting %s mapping information to directory "%s"', $args['to'], $args['dest']), 'INFO');
$exporter->setMetadatas($metadatas);
$exporter->export();
}
private function _isDoctrine1Schema(array $from)
{
$files = glob($from[0] . '/*.yml');
$files = glob(current($from) . '/*.yml');
if ($files) {
$array = \sfYaml::load($files[0]);
$first = current($array);
// We're dealing with a Doctrine 1 schema if you have
// a columns index in the first model array
return isset($first['columns']);
} else {
return false;
}
}
private function _determinePathType($path)
private function _determineSourceType($source)
{
$files = glob($path . '/*.*');
if (!$files)
{
throw new \InvalidArgumentException(sprintf('No schema mapping files found in "%s"', $path));
}
$contents = file_get_contents($files[0]);
if (preg_match("/class (.*)/", $contents)) {
return 'annotation';
} else {
$info = pathinfo($files[0]);
return $info['extension'];
}
// If the --from=<VALUE> is a directory lets determine if it is
// annotations, yaml, xml, etc.
if (is_dir($source)) {
// Find the files in the directory
$files = glob($source . '/*.*');
if ( ! $files) {
throw new \InvalidArgumentException(sprintf('No mapping files found in "%s"', $source));
}
// Get the contents of the first file
$contents = file_get_contents($files[0]);
// Check if it has a class definition in it for annotations
if (preg_match("/class (.*)/", $contents)) {
return 'annotation';
// Otherwise lets determine the type based on the extension of the
// first file in the directory (yml, xml, etc)
} else {
$info = pathinfo($files[0]);
return $info['extension'];
}
// Nothing special for database
} else if ($source == 'database') {
return 'database';
}
}
private function _getSourceByType($type, $source)
{
// If --from==database then the source is an instance of SchemaManager
// for the current EntityMAnager
if ($type == 'database') {
return $this->_em->getConnection()->getSchemaManager();
} else {
return $source;
}
}
}

View File

@ -110,8 +110,8 @@ class SchemaToolTask extends AbstractTask
$isDrop = isset($args['drop']);
$isUpdate = isset($args['update']);
if ( ! ($isCreate ^ $isDrop ^ $isUpdate)) {
$printer->writeln("One of --create, --drop or --update required, and only one.", 'ERROR');
if ($isUpdate && ($isCreate || $isDrop)) {
$printer->writeln("You can't use --update with --create or --drop", 'ERROR');
return false;
}
@ -174,7 +174,24 @@ class SchemaToolTask extends AbstractTask
$printer->writeln('No classes to process.', 'INFO');
return;
}
if ($isDrop) {
if (isset($args['dump-sql'])) {
foreach ($tool->getDropSchemaSql($classes) as $sql) {
$printer->writeln($sql);
}
} else {
$printer->writeln('Dropping database schema...', 'INFO');
try {
$tool->dropSchema($classes);
$printer->writeln('Database schema dropped successfully.', 'INFO');
} catch (\Exception $ex) {
throw new DoctrineException($ex);
}
}
}
if ($isCreate) {
if (isset($args['dump-sql'])) {
foreach ($tool->getCreateSchemaSql($classes) as $sql) {
@ -190,24 +207,9 @@ class SchemaToolTask extends AbstractTask
throw new DoctrineException($ex);
}
}
} else if ($isDrop) {
if (isset($args['dump-sql'])) {
foreach ($tool->getDropSchemaSql($classes) as $sql) {
$printer->writeln($sql);
}
} else {
$printer->writeln('Dropping database schema...', 'INFO');
try {
$tool->dropSchema($classes);
$printer->writeln('Database schema dropped successfully.', 'INFO');
} catch (\Exception $ex) {
throw new DoctrineException($ex);
}
}
} else if ($isUpdate) {
$printer->writeln("--update support is not yet fully implemented.", 'ERROR');
}
if ($isUpdate) {
if (isset($args['dump-sql'])) {
foreach ($tool->getUpdateSchemaSql($classes) as $sql) {
$printer->writeln($sql);

View File

@ -35,14 +35,14 @@ use Doctrine\ORM\Mapping\ClassMetadata;
* // and convert it to a single set of yaml files.
*
* $cme = new Doctrine\ORM\Tools\Export\ClassMetadataExporter();
* $cme->addMappingDirectory(__DIR__ . '/Entities', 'php');
* $cme->addMappingDirectory(__DIR__ . '/xml', 'xml');
* $cme->addMappingDirectory(__DIR__ . '/yaml', 'yaml');
* $cme->addMappingSource(__DIR__ . '/Entities', 'php');
* $cme->addMappingSource(__DIR__ . '/xml', 'xml');
* $cme->addMappingSource(__DIR__ . '/yaml', 'yaml');
*
* $exporter = $cme->getExporter('yaml');
* $exporter->setOutputDir(__DIR__ . '/new_yaml');
*
* $exporter->setMetadatas($cme->getMetadatasForMappingDirectories());
* $exporter->setMetadatas($cme->getMetadatasForMappingSources());
* $exporter->export();
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
@ -65,10 +65,11 @@ class ClassMetadataExporter
'annotation' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'yaml' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
'yml' => 'Doctrine\ORM\Mapping\Driver\YamlDriver',
'xml' => 'Doctrine\ORM\Mapping\Driver\XmlDriver'
'xml' => 'Doctrine\ORM\Mapping\Driver\XmlDriver',
'database' => 'Doctrine\ORM\Mapping\Driver\DatabaseDriver'
);
private $_mappingDirectories = array();
private $_mappingSources = array();
/**
* Add a new mapping directory to the array of directories to convert and export
@ -76,23 +77,24 @@ class ClassMetadataExporter
*
* [php]
* $cme = new Doctrine\ORM\Tools\Export\ClassMetadataExporter();
* $cme->addMappingDirectory(__DIR__ . '/yaml', 'yaml');
* $cme->addMappingSource(__DIR__ . '/yaml', 'yaml');
* $cme->addMappingSource($schemaManager, 'database');
*
* @param string $dir The path to the mapping files
* @param string $source The source for the mapping
* @param string $type The type of mapping files (yml, xml, etc.)
* @return void
*/
public function addMappingDirectory($dir, $type)
public function addMappingSource($source, $type)
{
if ($type === 'php') {
$this->_mappingDirectories[] = array($dir, $type);
$this->_mappingSources[] = array($source, $type);
} else {
if ( ! isset($this->_mappingDrivers[$type])) {
throw DoctrineException::invalidMappingDriverType($type);
}
$driver = $this->getMappingDriver($type, $dir);
$this->_mappingDirectories[] = array($dir, $driver);
$driver = $this->getMappingDriver($type, $source);
$this->_mappingSources[] = array($source, $driver);
}
}
@ -100,24 +102,26 @@ class ClassMetadataExporter
* Get an instance of a mapping driver
*
* @param string $type The type of mapping driver (yaml, xml, annotation, etc.)
* @param string $dir The directory to configure the driver to look in. Only required for file drivers (yml, xml).
* @param string $source The source for the driver
* @return AbstractDriver $driver
*/
public function getMappingDriver($type, $dir = null)
public function getMappingDriver($type, $source = null)
{
if ( ! isset($this->_mappingDrivers[$type])) {
return false;
}
$class = $this->_mappingDrivers[$type];
if (is_subclass_of($class, 'Doctrine\ORM\Mapping\Driver\AbstractFileDriver')) {
if (is_null($dir)) {
if (is_null($source)) {
throw DoctrineException::fileMappingDriversRequireDirectoryPath();
}
$driver = new $class($dir, constant($class . '::PRELOAD'));
} else {
$driver = new $class($source, constant($class . '::PRELOAD'));
} else if ($class == 'Doctrine\ORM\Mapping\Driver\AnnotationDriver') {
$reader = new \Doctrine\Common\Annotations\AnnotationReader(new \Doctrine\Common\Cache\ArrayCache);
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
$driver = new $class($reader);
$driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
} else if ($class == 'Doctrine\ORM\Mapping\Driver\DatabaseDriver') {
$driver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver($source);
}
return $driver;
}
@ -127,9 +131,9 @@ class ClassMetadataExporter
*
* @return array $mappingDirectories
*/
public function getMappingDirectories()
public function getMappingSources()
{
return $this->_mappingDirectories;
return $this->_mappingSources;
}
/**
@ -139,14 +143,14 @@ class ClassMetadataExporter
*
* @return array $classes
*/
public function getMetadatasForMappingDirectories()
public function getMetadatasForMappingSources()
{
$classes = array();
foreach ($this->_mappingDirectories as $d) {
list($dir, $driver) = $d;
foreach ($this->_mappingSources as $d) {
list($source, $driver) = $d;
if ($driver == 'php') {
$iter = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir),
$iter = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source),
\RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($iter as $item) {
@ -164,7 +168,7 @@ class ClassMetadataExporter
}
} else {
if ($driver instanceof \Doctrine\ORM\Mapping\Driver\AnnotationDriver) {
$iter = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir),
$iter = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source),
\RecursiveIteratorIterator::LEAVES_ONLY);
$declared = get_declared_classes();
@ -200,7 +204,6 @@ class ClassMetadataExporter
unset($classes[$key]);
}
}
$classes = array_values($classes);
return $classes;
}
@ -208,16 +211,16 @@ class ClassMetadataExporter
* Get a exporter driver instance
*
* @param string $type The type to get (yml, xml, etc.)
* @param string $dir The directory where the exporter will export to
* @param string $source The directory where the exporter will export to
* @return AbstractExporter $exporter
*/
public function getExporter($type, $dir = null)
public function getExporter($type, $source = null)
{
if ( ! isset($this->_exporterDrivers[$type])) {
throw DoctrineException::invalidExporterDriverType($type);
}
$class = $this->_exporterDrivers[$type];
return new $class($dir);
return new $class($source);
}
}

View File

@ -250,7 +250,10 @@ class AnnotationExporter extends AbstractExporter
$methods = array();
foreach ($metadata->fieldMappings as $fieldMapping) {
$this->_addMethod('set', $fieldMapping['fieldName'], $metadata, $methods);
if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id']) {
$this->_addMethod('set', $fieldMapping['fieldName'], $metadata, $methods);
}
$this->_addMethod('get', $fieldMapping['fieldName'], $metadata, $methods);
}
@ -302,6 +305,9 @@ class AnnotationExporter extends AbstractExporter
$lines[] = str_repeat(' ', $this->_numSpaces) . '/**';
$column = array();
if (isset($fieldMapping['columnName'])) {
$column[] = 'name="' . $fieldMapping['columnName'] . '"';
}
if (isset($fieldMapping['type'])) {
$column[] = 'type="' . $fieldMapping['type'] . '"';
}

View File

@ -145,11 +145,14 @@ class XmlExporter extends AbstractExporter
if (isset($field['scale'])) {
$fieldXml->addAttribute('scale', $field['scale']);
}
if (isset($field['unique'])) {
if (isset($field['unique']) && $field['unique']) {
$fieldXml->addAttribute('unique', $field['unique']);
}
if (isset($field['options'])) {
$fieldXml->addAttribute('options', $field['options']);
$optionsXml = $fieldXml->addChild('options');
foreach ($field['options'] as $key => $value) {
$optionsXml->addAttribute($key, $value);
}
}
if (isset($field['version'])) {
$fieldXml->addAttribute('version', $field['version']);
@ -225,6 +228,51 @@ class XmlExporter extends AbstractExporter
}
}
return $xml->asXml();
return $this->_asXml($xml);
}
/**
* Code originally taken from
* http://recurser.com/articles/2007/04/05/format-xml-with-php/
*
* @param string $simpleXml
* @return string $xml
*/
private function _asXml($simpleXml)
{
$xml = $simpleXml->asXml();
// add marker linefeeds to aid the pretty-tokeniser (adds a linefeed between all tag-end boundaries)
$xml = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $xml);
// now indent the tags
$token = strtok($xml, "\n");
$result = ''; // holds formatted version as it is built
$pad = 0; // initial indent
$matches = array(); // returns from preg_matches()
// test for the various tag states
while ($token !== false) {
// 1. open and closing tags on same line - no change
if (preg_match('/.+<\/\w[^>]*>$/', $token, $matches)) {
$indent = 0;
// 2. closing tag - outdent now
} else if (preg_match('/^<\/\w/', $token, $matches)) {
$pad = $pad - 4;
// 3. opening tag - don't pad this one, only subsequent tags
} elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $token, $matches)) {
$indent = 4;
// 4. no indentation needed
} else {
$indent = 0;
}
// pad the line with the required number of leading spaces
$line = str_pad($token, strlen($token)+$pad, ' ', STR_PAD_LEFT);
$result .= $line . "\n"; // add to the cumulative result, with linefeed
$token = strtok("\n"); // get the next token
$pad += $indent; // update the pad size for subsequent lines
}
return $result;
}
}

View File

@ -70,7 +70,10 @@ class YamlExporter extends AbstractExporter
$array['schema'] = $metadata->primaryTable['schema'];
}
$array['inheritanceType'] = $this->_getInheritanceTypeString($metadata->getInheritanceType());
$inheritanceType = $metadata->getInheritanceType();
if ($inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
$array['inheritanceType'] = $this->_getInheritanceTypeString($inheritanceType);
}
if ($column = $metadata->getDiscriminatorColumn()) {
$array['discriminatorColumn'] = $column;
@ -80,7 +83,9 @@ class YamlExporter extends AbstractExporter
$array['discriminatorMap'] = $map;
}
$array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy);
if ($metadata->changeTrackingPolicy !== ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT) {
$array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy);
}
if (isset($metadata->primaryTable['indexes'])) {
$array['indexes'] = $metadata->primaryTable['indexes'];
@ -92,27 +97,48 @@ class YamlExporter extends AbstractExporter
}
}
$fields = $metadata->fieldMappings;
$fieldMappings = $metadata->fieldMappings;
$id = array();
foreach ($fields as $name => $field) {
if (isset($field['id']) && $field['id']) {
$id[$name] = $field;
unset($fields[$name]);
$ids = array();
foreach ($fieldMappings as $name => $fieldMapping) {
if (isset($fieldMapping['length'])) {
$fieldMapping['type'] = $fieldMapping['type'] . '(' . $fieldMapping['length'] . ')';
unset($fieldMapping['length']);
}
unset($fieldMapping['fieldName']);
if ($fieldMapping['columnName'] == $name) {
unset($fieldMapping['columnName']);
}
if (isset($fieldMapping['id']) && $fieldMapping['id']) {
$ids[$name] = $fieldMapping;
unset($fieldMappings[$name]);
continue;
}
$fieldMappings[$name] = $fieldMapping;
}
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$id[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
$ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
}
$array['id'] = $id;
$array['fields'] = $fields;
if ($ids) {
$array['fields'] = $ids;
}
if ($fieldMappings) {
if ( ! isset($array['fields'])) {
$array['fields'] = array();
}
$array['fields'] = array_merge($array['fields'], $fieldMappings);
}
$associations = array();
foreach ($metadata->associationMappings as $name => $associationMapping) {
$associationMappingArray = array(
'fieldName' => $associationMapping->sourceFieldName,
'targetEntity' => $associationMapping->targetEntityName,
'cascade' => array(
'remove' => $associationMapping->isCascadeRemove,
@ -124,9 +150,14 @@ class YamlExporter extends AbstractExporter
);
if ($associationMapping instanceof OneToOneMapping) {
$joinColumns = $associationMapping->joinColumns;
$newJoinColumns = array();
foreach ($joinColumns as $joinColumn) {
$newJoinColumns[$joinColumn['name']]['referencedColumnName'] = $joinColumn['referencedColumnName'];
}
$oneToOneMappingArray = array(
'mappedBy' => $associationMapping->mappedByFieldName,
'joinColumns' => $associationMapping->joinColumns,
'joinColumns' => $newJoinColumns,
'orphanRemoval' => $associationMapping->orphanRemoval,
);
@ -137,7 +168,7 @@ class YamlExporter extends AbstractExporter
'mappedBy' => $associationMapping->mappedByFieldName,
'orphanRemoval' => $associationMapping->orphanRemoval,
);
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
$array['oneToMany'][$name] = $associationMappingArray;
} else if ($associationMapping instanceof ManyToManyMapping) {

View File

@ -50,19 +50,19 @@ class ConvertDoctrine1SchemaTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue(file_exists(__DIR__ . '/convert/User.dcm.yml'));
$this->assertTrue(file_exists(__DIR__ . '/convert/Profile.dcm.yml'));
$cme->addMappingDirectory(__DIR__ . '/convert', 'yml');
$metadatas = $cme->getMetadatasForMappingDirectories();
$cme->addMappingSource(__DIR__ . '/convert', 'yml');
$metadatas = $cme->getMetadatasForMappingSources();
$this->assertEquals(2, count($metadatas));
$this->assertEquals('Profile', $metadatas[0]->name);
$this->assertEquals('User', $metadatas[1]->name);
$this->assertEquals(4, count($metadatas[0]->fieldMappings));
$this->assertEquals(3, count($metadatas[1]->fieldMappings));
$this->assertEquals('Profile', $metadatas['Profile']->name);
$this->assertEquals('User', $metadatas['User']->name);
$this->assertEquals(4, count($metadatas['Profile']->fieldMappings));
$this->assertEquals(3, count($metadatas['User']->fieldMappings));
$this->assertEquals('Profile', $metadatas[0]->associationMappings['User']->sourceEntityName);
$this->assertEquals('\User', $metadatas[0]->associationMappings['User']->targetEntityName);
$this->assertEquals('Profile', $metadatas['Profile']->associationMappings['User']->sourceEntityName);
$this->assertEquals('\User', $metadatas['Profile']->associationMappings['User']->targetEntityName);
$this->assertEquals('username', $metadatas[1]->primaryTable['indexes']['username']['columns'][0]);
$this->assertEquals('username', $metadatas['User']->primaryTable['indexes']['username']['columns'][0]);
unlink(__DIR__ . '/convert/User.dcm.yml');
unlink(__DIR__ . '/convert/Profile.dcm.yml');

View File

@ -61,25 +61,25 @@ class ClassMetadataExporterTest extends \Doctrine\Tests\OrmTestCase
public function testAddMappingDirectory()
{
$cme = new ClassMetadataExporter();
$cme->addMappingDirectory(__DIR__ . '/annotation', 'annotation');
$cme->addMappingDirectory(__DIR__ . '/php', 'php');
$cme->addMappingDirectory(__DIR__ . '/xml', 'xml');
$cme->addMappingDirectory(__DIR__ . '/yml', 'yml');
$cme->addMappingSource(__DIR__ . '/annotation', 'annotation');
$cme->addMappingSource(__DIR__ . '/php', 'php');
$cme->addMappingSource(__DIR__ . '/xml', 'xml');
$cme->addMappingSource(__DIR__ . '/yml', 'yml');
$mappingDirectories = $cme->getMappingDirectories();
$this->assertEquals(4, count($mappingDirectories));
$mappingSources = $cme->getMappingSources();
$this->assertEquals(4, count($mappingSources));
$this->assertEquals($mappingDirectories[0][0], __DIR__.'/annotation');
$this->assertTrue($mappingDirectories[0][1] instanceof \Doctrine\ORM\Mapping\Driver\AnnotationDriver);
$this->assertEquals($mappingSources[0][0], __DIR__.'/annotation');
$this->assertTrue($mappingSources[0][1] instanceof \Doctrine\ORM\Mapping\Driver\AnnotationDriver);
$this->assertEquals($mappingDirectories[1][0], __DIR__.'/php');
$this->assertEquals('php', $mappingDirectories[1][1]);
$this->assertEquals($mappingSources[1][0], __DIR__.'/php');
$this->assertEquals('php', $mappingSources[1][1]);
$this->assertEquals($mappingDirectories[2][0], __DIR__.'/xml');
$this->assertTrue($mappingDirectories[2][1] instanceof \Doctrine\ORM\Mapping\Driver\XmlDriver);
$this->assertEquals($mappingSources[2][0], __DIR__.'/xml');
$this->assertTrue($mappingSources[2][1] instanceof \Doctrine\ORM\Mapping\Driver\XmlDriver);
$this->assertEquals($mappingDirectories[3][0], __DIR__.'/yml');
$this->assertTrue($mappingDirectories[3][1] instanceof \Doctrine\ORM\Mapping\Driver\YamlDriver);
$this->assertEquals($mappingSources[3][0], __DIR__.'/yml');
$this->assertTrue($mappingSources[3][1] instanceof \Doctrine\ORM\Mapping\Driver\YamlDriver);
}
/**
@ -89,16 +89,16 @@ class ClassMetadataExporterTest extends \Doctrine\Tests\OrmTestCase
public function testGetMetadataInstances()
{
$cme = new ClassMetadataExporter();
$cme->addMappingDirectory(__DIR__ . '/php', 'php');
$cme->addMappingDirectory(__DIR__ . '/xml', 'xml');
$cme->addMappingDirectory(__DIR__ . '/yml', 'yml');
$cme->addMappingSource(__DIR__ . '/php', 'php');
$cme->addMappingSource(__DIR__ . '/xml', 'xml');
$cme->addMappingSource(__DIR__ . '/yml', 'yml');
$metadataInstances = $cme->getMetadatasForMappingDirectories();
$metadataInstances = $cme->getMetadatasForMappingSources();
$this->assertEquals(3, count($metadataInstances));
$this->assertEquals('PhpTest', $metadataInstances[0]->name);
$this->assertEquals('XmlTest', $metadataInstances[1]->name);
$this->assertEquals('YmlTest', $metadataInstances[2]->name);
$this->assertEquals('PhpTest', $metadataInstances['PhpTest']->name);
$this->assertEquals('XmlTest', $metadataInstances['XmlTest']->name);
$this->assertEquals('YmlTest', $metadataInstances['YmlTest']->name);
}
/**
@ -116,14 +116,14 @@ class ClassMetadataExporterTest extends \Doctrine\Tests\OrmTestCase
$types = array('annotation', 'php', 'xml', 'yml');
$cme = new ClassMetadataExporter();
$cme->addMappingDirectory(__DIR__ . '/php', 'php');
$cme->addMappingDirectory(__DIR__ . '/xml', 'xml');
$cme->addMappingDirectory(__DIR__ . '/yml', 'yml');
$cme->addMappingSource(__DIR__ . '/php', 'php');
$cme->addMappingSource(__DIR__ . '/xml', 'xml');
$cme->addMappingSource(__DIR__ . '/yml', 'yml');
foreach ($types as $type) {
// Export the above mapping directories to the type
$exporter = $cme->getExporter($type, __DIR__ . '/export/' . $type);
$exporter->setMetadatas($cme->getMetadatasForMappingDirectories());
$exporter->setMetadatas($cme->getMetadatasForMappingSources());
$exporter->export();
// Make sure the files were written
@ -133,12 +133,12 @@ class ClassMetadataExporterTest extends \Doctrine\Tests\OrmTestCase
// Try and read back in the exported mapping files to make sure they are valid
$cme2 = new ClassMetadataExporter();
$cme2->addMappingDirectory(__DIR__ . '/export/' . $type, $type);
$metadataInstances = $cme2->getMetadatasForMappingDirectories();
$cme2->addMappingSource(__DIR__ . '/export/' . $type, $type);
$metadataInstances = $cme2->getMetadatasForMappingSources();
$this->assertEquals(3, count($metadataInstances));
$this->assertEquals('PhpTest', $metadataInstances[0]->name);
$this->assertEquals('XmlTest', $metadataInstances[1]->name);
$this->assertEquals('YmlTest', $metadataInstances[2]->name);
$this->assertEquals('PhpTest', $metadataInstances['PhpTest']->name);
$this->assertEquals('XmlTest', $metadataInstances['XmlTest']->name);
$this->assertEquals('YmlTest', $metadataInstances['YmlTest']->name);
// Cleanup
unlink(__DIR__ . '/export/' . $type . '/PhpTest'.$exporter->getExtension());

View File

@ -2,7 +2,7 @@
namespace Entities;
/** @Entity @Table(name="users") */
/** @Entity @Table(name="users", indexes={@Index(name="name_idx", columns={"name", "test"})}) */
class User {
/**
* @Id @Column(type="integer")
@ -11,6 +11,8 @@ class User {
private $id;
/** @Column(type="string", length=50) */
private $name;
/** @Column(type="string", length=50) */
private $test;
/**
* @OneToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")

View File

@ -23,8 +23,10 @@ $config = new \Doctrine\ORM\Configuration();
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'doctrine2'
);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);