. */ Phar::mapPhar(); require_once 'phar://'.__FILE__.'/Doctrine/Common/ClassLoader.php'; $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', 'phar://'.__FILE__); $classLoader->register(); $classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', 'phar://'.__FILE__); $classLoader->register(); $classLoader = new \Doctrine\Common\ClassLoader('Symfony', 'phar://'.__FILE__); $classLoader->register(); $helperSet = new \Symfony\Component\Console\Helper\HelperSet(array( 'dialog' => new \Symfony\Component\Console\Helper\DialogHelper(), )); $cli = new \Symfony\Component\Console\Application('Doctrine Migrations', \Doctrine\DBAL\Migrations\MigrationsVersion::VERSION); $cli->setCatchExceptions(true); $cli->setHelperSet($helperSet); $cli->addCommands(array( // Migrations Commands new \Doctrine\DBAL\Migrations\Tools\Console\Command\DiffCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\ExecuteCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\GenerateCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand() )); $input = file_exists('migrations-input.php') ? include('migrations-input.php') : null; $output = file_exists('migrations-output.php') ? include('migrations-output.php') : null; $cli->run($input, $output); __HALT_COMPILER(); ?> ,;Doctrine/DBAL/Migrations/IrreversibleMigrationException.phpLܶ4Doctrine/DBAL/Migrations/AbortMigrationException.phpmLm3Doctrine/DBAL/Migrations/SkipMigrationException.phplLlTŶ.Doctrine/DBAL/Migrations/AbstractMigration.phpL`/Doctrine/DBAL/Migrations/MigrationException.php L (Shz&Doctrine/DBAL/Migrations/Migration.phpL~8Doctrine/DBAL/Migrations/Configuration/Configuration.php"7L"7p됶;Doctrine/DBAL/Migrations/Configuration/XmlConfiguration.php L Z<Doctrine/DBAL/Migrations/Configuration/YamlConfiguration.php L >x7DDoctrine/DBAL/Migrations/Configuration/AbstractFileConfiguration.php L M)Doctrine/DBAL/Migrations/OutputWriter.phpL44$Doctrine/DBAL/Migrations/Version.php&L&vM}.Doctrine/DBAL/Migrations/MigrationsVersion.php8L8R>Doctrine/DBAL/Migrations/Tools/Console/Command/DiffCommand.phpL4&BDoctrine/DBAL/Migrations/Tools/Console/Command/AbstractCommand.phpL*T\jBDoctrine/DBAL/Migrations/Tools/Console/Command/GenerateCommand.phpL~ADoctrine/DBAL/Migrations/Tools/Console/Command/VersionCommand.phpLADoctrine/DBAL/Migrations/Tools/Console/Command/ExecuteCommand.phpLUI@Doctrine/DBAL/Migrations/Tools/Console/Command/StatusCommand.phpLe-ADoctrine/DBAL/Migrations/Tools/Console/Command/MigrateCommand.php7L7a鹶Doctrine/DBAL/DBALException.php L ^<)Doctrine/DBAL/Driver/PDOOracle/Driver.php L s&-Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php L ƶDoctrine/DBAL/Schema/Column.phpL+W#Doctrine/DBAL/Schema/Constraint.phpOLO:#Doctrine/DBAL/Schema/SchemaDiff.phpL~'׶Doctrine/DBAL/Schema/Schema.php3"L3""$V+Doctrine/DBAL/Schema/MsSqlSchemaManager.php!L!@ᄶ(Doctrine/DBAL/Schema/Visitor/Visitor.php* L* Di&9Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.phpjLj¶7Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.phpL0-Doctrine/DBAL/Schema/ForeignKeyConstraint.phpL}WDoctrine/DBAL/Schema/Table.php`CL`CP"Doctrine/DBAL/Schema/TableDiff.php^L^,Doctrine/DBAL/Schema/OracleSchemaManager.php6$L6$G!Doctrine/DBAL/Schema/Sequence.phpLLk#Doctrine/DBAL/Schema/ColumnDiff.phpL'(Doctrine/DBAL/Schema/SchemaException.phptLtή!Doctrine/DBAL/LockMode.phpLjWW̶Doctrine/DBAL/Driver.php= L= 1].Doctrine/DBAL/Platforms/PostgreSqlPlatform.phpYLYv)Doctrine/DBAL/Platforms/MsSqlPlatform.phpMLM>0*Doctrine/DBAL/Platforms/OraclePlatform.phpVLV|'Doctrine/DBAL/Platforms/DB2Platform.phpALA\E1߶)Doctrine/DBAL/Platforms/MySqlPlatform.phpMLM 6,Doctrine/DBAL/Platforms/AbstractPlatform.phpLX*Doctrine/DBAL/Platforms/SqlitePlatform.phpF8LF8E TDoctrine/DBAL/README.markdownLDoctrine/DBAL/Version.phpL|!%Doctrine/DBAL/ConnectionException.phpLCDoctrine/DBAL/Connection.php{L{'Doctrine/DBAL/Logging/EchoSQLLogger.phpNLNN$Doctrine/DBAL/Logging/DebugStack.phpL}#Doctrine/DBAL/Logging/SQLLogger.phpL5 5Doctrine/DBAL/Tools/Console/Command/ImportCommand.phpLG:5Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php/ L/ 9R7Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.phpvLv4s'Doctrine/DBAL/Types/VarDateTimeType.phpLw3#Doctrine/DBAL/Types/IntegerType.phpL l6 Doctrine/DBAL/Types/DateType.phpLEeDoctrine/DBAL/Types/Type.phpL,do!Doctrine/DBAL/Types/FloatType.phpzLz~[e$Doctrine/DBAL/Types/SmallIntType.phpLG9#k"Doctrine/DBAL/Types/BigIntType.phpL>"Doctrine/DBAL/Types/StringType.phpYLY(.G"Doctrine/DBAL/Types/ObjectType.phpLM(&Doctrine/DBAL/Types/DateTimeTzType.php L }+Doctrine/DBAL/Types/ConversionException.phpLGf[$Doctrine/DBAL/Types/DateTimeType.phpL݁#Doctrine/DBAL/Types/DecimalType.phpL Doctrine/DBAL/Types/TextType.phpL@!Doctrine/DBAL/Types/ArrayType.phpL#Doctrine/DBAL/Types/BooleanType.phpL<4 Doctrine/DBAL/Types/TimeType.php5L5ٻ+Doctrine/DBAL/Event/ConnectionEventArgs.phpL52Doctrine/DBAL/Event/Listeners/MysqlSessionInit.phpO LO 93Doctrine/DBAL/Event/Listeners/OracleSessionInit.php L 1?Doctrine/Common/EventArgs.php# L# to#Doctrine/Common/CommonException.phpqLq(|k0Doctrine/Common/Annotations/AnnotationReader.php"L" 63Doctrine/Common/Annotations/AnnotationException.phpL=?&Doctrine/Common/Annotations/Parser.php=L=3S%Doctrine/Common/Annotations/Lexer.phpLSn*Doctrine/Common/Annotations/Annotation.php L @Doctrine/Common/ClassLoader.php"L":@/Doctrine/Common/Collections/ArrayCollection.php.L.j*Doctrine/Common/Collections/Collection.phpZ LZ Doctrine/Common/Version.phpL/\%Doctrine/Common/Cache/XcacheCache.php L &2Ͷ$Doctrine/Common/Cache/ArrayCache.php' L' ::b'Doctrine/Common/Cache/MemcacheCache.php L r6Doctrine/Common/Cache/Cache.php L \O"Doctrine/Common/Cache/ApcCache.phpA LA Y*'Doctrine/Common/Cache/AbstractCache.php1L1L0Doctrine/Common/Util/Debug.phpL;`"Doctrine/Common/Util/Inflector.php; L; ;%됶)Doctrine/Common/NotifyPropertyChanged.phpLnF+Doctrine/Common/PropertyChangedListener.phpL Doctrine/Common/EventManager.php,L,g]FDoctrine/Common/Lexer.phpKLKN#Doctrine/Common/EventSubscriber.phpLiJSymfony/Component/Yaml/Yaml.php L !!Symfony/Component/Yaml/Dumper.phpLÂ*Symfony/Component/Yaml/ParserException.phpL9$Symfony/Component/Yaml/Exception.php L WGQ!Symfony/Component/Yaml/Inline.php*L*:ڶ!Symfony/Component/Yaml/Parser.php?L?|#ɶ1Symfony/Component/Console/Command/ListCommand.phpL[1Symfony/Component/Console/Command/HelpCommand.phpiLiLU-Symfony/Component/Console/Command/Command.phpR8LR8Dj2Symfony/Component/Console/Output/ConsoleOutput.phpiLiw*4Symfony/Component/Console/Output/OutputInterface.phpLw 1Symfony/Component/Console/Output/StreamOutput.php L J+Symfony/Component/Console/Output/Output.phpLvs /Symfony/Component/Console/Output/NullOutput.phpL1#Symfony/Component/Console/Shell.php!L!ɖ4Symfony/Component/Console/Helper/FormatterHelper.php L g.Symfony/Component/Console/Helper/HelperSet.php L Ƕ+Symfony/Component/Console/Helper/Helper.phpLܶ4Symfony/Component/Console/Helper/HelperInterface.phpLf 1Symfony/Component/Console/Helper/DialogHelper.php L 3.϶)Symfony/Component/Console/Application.php[L[N6Symfony/Component/Console/Tester/ApplicationTester.phpb Lb *2Symfony/Component/Console/Tester/CommandTester.php8 L8 ե)Symfony/Component/Console/Input/Input.php~L~)[.Symfony/Component/Console/Input/ArrayInput.php L glt/Symfony/Component/Console/Input/InputOption.phpALA63Symfony/Component/Console/Input/InputDefinition.phpE<LE<.2Symfony/Component/Console/Input/InputInterface.phpL\)--Symfony/Component/Console/Input/ArgvInput.phpLM/Symfony/Component/Console/Input/StringInput.php L gkd1Symfony/Component/Console/Input/InputArgument.php L ]TP. */ namespace Doctrine\DBAL\Migrations; /** * Exception to be thrown in the down() methods of migrations that signifies it * is an irreversible migration and stops execution. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class IrreversibleMigrationException extends \Exception { }. */ namespace Doctrine\DBAL\Migrations; use Doctrine\DBAL\Schema\Schema, Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Migrations\Version; /** * Abstract class for individual migrations to extend from. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ abstract class AbstractMigration { /** The Migrations Configuration instance for this migration */ protected $_configuration; /** The OutputWriter object instance used for outputting information */ protected $_outputWriter; /** The Doctrine\DBAL\Connection instance we are migrating */ protected $_connection; /** Reference to the SchemaManager instance referened by $_connection */ protected $_sm; /** Reference to the DatabasePlatform instance referenced by $_conection */ protected $_platform; /** Reference to the Version instance representing this migration */ protected $_version; public function __construct(Version $version) { $this->_configuration = $version->getConfiguration(); $this->_outputWriter = $this->_configuration->getOutputWriter(); $this->_connection = $this->_configuration->getConnection(); $this->_sm = $this->_connection->getSchemaManager(); $this->_platform = $this->_connection->getDatabasePlatform(); $this->_version = $version; } abstract public function up(Schema $schema); abstract public function down(Schema $schema); protected function _addSql($sql) { return $this->_version->addSql($sql); } protected function _write($message) { $this->_outputWriter->write($message); } protected function _throwIrreversibleMigrationException($message = null) { if ($message === null) { $message = 'This migration is irreversible and cannot be reverted.'; } throw new IrreversibleMigrationException($message); } /** * Print a warning message if the condition evalutes to TRUE. * * @param bool $condition * @param string $message */ public function warnIf($condition, $message = '') { $message = (strlen($message)) ? $message : 'Unknown Reason'; if ($condition === true) { $this->_outputWriter->write(' Warning during ' . $this->_version->getExecutionState() . ': ' . $message . ''); } } /** * Abort the migration if the condition evalutes to TRUE. * * @param bool $condition * @param string $message */ public function abortIf($condition, $message = '') { $message = (strlen($message)) ? $message : 'Unknown Reason'; if ($condition === true) { throw new AbortMigrationException($message); } } /** * Skip this migration (but not the next ones) if condition evalutes to TRUE. * * @param bool $condition * @param string $message */ public function skipIf($condition, $message = '') { $message = (strlen($message)) ? $message : 'Unknown Reason'; if ($condition === true) { throw new SkipMigrationException($message); } } public function preUp(Schema $schema) { } public function postUp(Schema $schema) { } public function preDown(Schema $schema) { } public function postDown(Schema $schema) { } }. */ namespace Doctrine\DBAL\Migrations; /** * Class for Migrations specific exceptions * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class MigrationException extends \Exception { public static function migrationsNamespaceRequired() { return new self('Migrations namespace must be configured in order to use Doctrine migrations.', 2); } public static function migrationsDirectoryRequired() { return new self('Migrations directory must be configured in order to use Doctrine migrations.', 3); } public static function noMigrationsToExecute() { return new self('Could not find any migrations to execute.', 4); } public static function unknownMigrationVersion($version) { return new self(sprintf('Could not find migration version %s', $version), 5); } public static function alreadyAtVersion($version) { return new self(sprintf('Database is already at version %s', $version), 6); } public static function duplicateMigrationVersion($version, $class) { return new self(sprintf('Migration version %s already registered with class %s', $version, $class), 7); } public static function configurationFileAlreadyLoaded() { return new self(sprintf('Migrations configuration file already loaded'), 8); } } . */ namespace Doctrine\DBAL\Migrations; use Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Schema\Schema; /** * Class for running migrations to the current version or a manually specified version. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class Migration { /** The Doctrine\DBAL\Connection instance we are migrating */ private $_connection; /** The OutputWriter object instance used for outputting information */ private $_outputWriter; /** * Construct a Migration instance * * @param Configuration $configuration A migration Configuration instance */ public function __construct(Configuration $configuration) { $this->_configuration = $configuration; $this->_outputWriter = $configuration->getOutputWriter(); } /** * Get the array of versions and SQL queries that would be executed for * each version but do not execute anything. * * @param string $to The version to migrate to. * @return array $sql The array of SQL queries. */ public function getSql($to = null) { return $this->migrate($to, true); } /** * Write a migration SQL file to the given path * * @param string $path The path to write the migration SQL file. * @param string $to The version to migrate to. * @return bool $written */ public function writeSqlFile($path, $to = null) { $sql = $this->getSql($to); $from = $this->_configuration->getCurrentVersion(); if ($to === null) { $to = $this->_configuration->getLatestVersion(); } $string = sprintf("# Doctrine Migration File Generated on %s\n", date('Y-m-d H:m:s')); $string .= sprintf("# Migrating from %s to %s\n", $from, $to); foreach ($sql as $version => $queries) { $string .= "\n# Version " . $version . "\n"; foreach ($queries as $query) { $string .= $query . ";\n"; } } if (is_dir($path)) { $path = realpath($path); $path = $path . '/doctrine_migration_' . date('YmdHis') . '.sql'; } $this->_outputWriter->write("\n".sprintf('Writing migration file to "%s"', $path)); return file_put_contents($path, $string); } /** * Run a migration to the current version or the given target version. * * @param string $to The version to migrate to. * @param string $dryRun Whether or not to make this a dry run and not execute anything. * @return array $sql The array of migration sql statements * @throws MigrationException */ public function migrate($to = null, $dryRun = false) { if ($to === null) { $to = $this->_configuration->getLatestVersion(); } $from = $this->_configuration->getCurrentVersion(); $from = (string) $from; $to = (string) $to; $migrations = $this->_configuration->getMigrations(); if ( ! isset($migrations[$to]) && $to > 0) { throw MigrationException::unknownMigrationVersion($to); } if ($from === $to) { throw MigrationException::alreadyAtVersion($to); } $direction = $from > $to ? 'down' : 'up'; $migrations = $this->_configuration->getMigrationsToExecute($direction, $to); if ($dryRun === false) { $this->_outputWriter->write(sprintf('Migrating %s to %s from %s', $direction, $to, $from)); } else { $this->_outputWriter->write(sprintf('Executing dry run of migration %s to %s from %s', $direction, $to, $from)); } if (empty($migrations)) { throw MigrationException::noMigrationsToExecute(); } $sql = array(); $time = 0; foreach ($migrations as $version) { $versionSql = $version->execute($direction, $dryRun); $sql[$version->getVersion()] = $versionSql; $time += $version->getTime(); } $this->_outputWriter->write("\n ------------------------\n"); $this->_outputWriter->write(sprintf(" ++ finished in %s", $time)); $this->_outputWriter->write(sprintf(" ++ %s migrations executed", count($migrations))); $this->_outputWriter->write(sprintf(" ++ %s sql queries", count($sql, true) - count($sql))); return $sql; } } . */ namespace Doctrine\DBAL\Migrations\Configuration; use Doctrine\DBAL\Connection, Doctrine\DBAL\Migrations\MigrationException, Doctrine\DBAL\Migrations\Version, Doctrine\DBAL\Migrations\OutputWriter, Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Column, Doctrine\DBAL\Types\Type; /** * Default Migration Configurtion object used for configuring an instance of * the Migration class. Set the connection, version table name, register migration * classes/versions, etc. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class Configuration { /** Name of this set of migrations */ private $_name; /** Flag for whether or not the migration table has been created */ private $_migrationTableCreated = false; /** Connection instance to use for migrations */ private $_connection; /** OutputWriter instance for writing output during migrations */ private $_outputWriter; /** The migration table name to track versions in */ private $_migrationsTableName = 'doctrine_migration_versions'; /** The path to a directory where new migration classes will be written */ private $_migrationsDirectory; /** Namespace the migration classes live in */ private $_migrationsNamespace; /** Array of the registered migrations */ private $_migrations = array(); /** * Construct a migration configuration object. * * @param Connection $connection A Connection instance * @param OutputWriter $outputWriter A OutputWriter instance */ public function __construct(Connection $connection, OutputWriter $outputWriter = null) { $this->_connection = $connection; if ($outputWriter === null) { $outputWriter = new OutputWriter(); } $this->_outputWriter = $outputWriter; } /** * Validation that this instance has all the required properties configured * * @return void * @throws MigrationException */ public function validate() { if ( ! $this->_migrationsNamespace) { throw MigrationException::migrationsNamespaceRequired(); } if ( ! $this->_migrationsDirectory) { throw MigrationException::migrationsDirectoryRequired(); } } /** * Set the name of this set of migrations * * @param string $name The name of this set of migrations */ public function setName($name) { $this->_name = $name; } /** * Returns the name of this set of migrations * * @return string $name The name of this set of migrations */ public function getName() { return $this->_name; } /** * Returns the OutputWriter instance * * @return OutputWriter $outputWriter The OutputWriter instance */ public function getOutputWriter() { return $this->_outputWriter; } /** * Returns a timestamp version as a formatted date * * @param string $version * @return string $formattedVersion The formatted version */ public function formatVersion($version) { return sprintf('%s-%s-%s %s:%s:%s', substr($version, 0, 4), substr($version, 4, 2), substr($version, 6, 2), substr($version, 8, 2), substr($version, 10, 2), substr($version, 12, 2) ); } /** * Returns the Connection instance * * @return Connection $connection The Connection instance */ public function getConnection() { return $this->_connection; } /** * Set the migration table name * * @param string $tableName The migration table name */ public function setMigrationsTableName($tableName) { $this->_migrationsTableName = $tableName; } /** * Returns the migration table name * * @return string $migrationsTableName The migration table name */ public function getMigrationsTableName() { return $this->_migrationsTableName; } /** * Set the new migrations directory where new migration classes are generated * * @param string $migrationsDirectory The new migrations directory */ public function setMigrationsDirectory($migrationsDirectory) { $this->_migrationsDirectory = $migrationsDirectory; } /** * Returns the new migrations directory where new migration classes are generated * * @return string $migrationsDirectory The new migrations directory */ public function getMigrationsDirectory() { return $this->_migrationsDirectory; } /** * Set the migrations namespace * * @param string $migrationsNamespace The migrations namespace */ public function setMigrationsNamespace($migrationsNamespace) { $this->_migrationsNamespace = $migrationsNamespace; } /** * Returns the migrations namespace * * @return string $migrationsNamespace The migrations namespace */ public function getMigrationsNamespace() { return $this->_migrationsNamespace; } /** * Register migrations from a given directory. Recursively finds all files * with the pattern VersionYYYYMMDDHHMMSS.php as the filename and registers * them as migrations. * * @param string $path The root directory to where some migration classes live. * @return $migrations The array of migrations registered. */ public function registerMigrationsFromDirectory($path) { $path = realpath($path); $path = rtrim($path, '/'); $files = glob($path . '/Version*.php'); $versions = array(); foreach ($files as $file) { require_once($file); $info = pathinfo($file); $version = substr($info['filename'], 7); $class = $this->_migrationsNamespace . '\\' . $info['filename']; $versions[] = $this->registerMigration($version, $class); } return $versions; } /** * Register a single migration version to be executed by a AbstractMigration * class. * * @param string $version The version of the migration in the format YYYYMMDDHHMMSS. * @param string $class The migration class to execute for the version. */ public function registerMigration($version, $class) { $version = (string) $version; $class = (string) $class; if (isset($this->_migrations[$version])) { throw MigrationException::duplicateMigrationVersion($version, get_class($this->_migrations[$version])); } $version = new Version($this, $version, $class); $this->_migrations[$version->getVersion()] = $version; ksort($this->_migrations); return $version; } /** * Register an array of migrations. Each key of the array is the version and * the value is the migration class name. * * * @param array $migrations * @return void */ public function registerMigrations(array $migrations) { $versions = array(); foreach ($migrations as $version => $class) { $versions[] = $this->registerMigration($version, $class); } return $versions; } /** * Get the array of registered migration versions. * * @return array $migrations */ public function getMigrations() { return $this->_migrations; } /** * Returns the Version instance for a given version in the format YYYYMMDDHHMMSS. * * @param string $version The version string in the format YYYYMMDDHHMMSS. * @return Version $version * @throws MigrationException $exception Throws exception if migration version does not exist. */ public function getVersion($version) { if ( ! isset($this->_migrations[$version])) { MigrationException::unknownMigrationVersion($version); } return $this->_migrations[$version]; } /** * Check if a version exists. * * @param string $version * @return bool $exists */ public function hasVersion($version) { return isset($this->_migrations[$version]) ? true : false; } /** * Check if a version has been migrated or not yet * * @param Version $version * @return bool $migrated */ public function hasVersionMigrated(Version $version) { $this->createMigrationTable(); $version = $this->_connection->fetchColumn("SELECT version FROM " . $this->_migrationsTableName . " WHERE version = '" . $version->getVersion() . "'"); return $version !== false ? true : false; } /** * Returns the current migrated version from the versions table. * * @return bool $currentVersion */ public function getCurrentVersion() { $this->createMigrationTable(); $result = $this->_connection->fetchColumn("SELECT version FROM " . $this->_migrationsTableName . " ORDER BY version DESC LIMIT 1"); return $result !== false ? (string) $result : '0'; } /** * Returns the total number of executed migration versions * * @return integer $count */ public function getNumberOfExecutedMigrations() { $this->createMigrationTable(); $result = $this->_connection->fetchColumn("SELECT COUNT(version) FROM " . $this->_migrationsTableName); return $result !== false ? $result : 0; } /** * Returns the total number of available migration versions * * @return integer $count */ public function getNumberOfAvailableMigrations() { return count($this->_migrations); } /** * Returns the latest available migration version. * * @return string $version The version string in the format YYYYMMDDHHMMSS. */ public function getLatestVersion() { $versions = array_keys($this->_migrations); $latest = end($versions); return $latest !== false ? (string) $latest : '0'; } /** * Create the migration table to track migrations with. * * @return bool $created Whether or not the table was created. */ public function createMigrationTable() { $this->validate(); if ($this->_migrationTableCreated) { return false; } $schema = $this->_connection->getSchemaManager()->createSchema(); if ( ! $schema->hasTable($this->_migrationsTableName)) { $columns = array( 'version' => new Column('version', Type::getType('string'), array('length' => 14)), ); $table = new Table($this->_migrationsTableName, $columns); $table->setPrimaryKey(array('version')); $this->_connection->getSchemaManager()->createTable($table); $this->_migrationTableCreated = true; return true; } return false; } /** * Returns the array of migrations to executed based on the given direction * and target version number. * * @param string $direction The direction we are migrating. * @param string $to The version to migrate to. * @return array $migrations The array of migrations we can execute. */ public function getMigrationsToExecute($direction, $to) { if ($direction === 'down') { $allVersions = array_reverse(array_keys($this->_migrations)); $classes = array_reverse(array_values($this->_migrations)); $allVersions = array_combine($allVersions, $classes); } else { $allVersions = $this->_migrations; } $versions = array(); foreach ($allVersions as $version) { if ($this->_shouldExecuteMigration($direction, $version, $to)) { $versions[$version->getVersion()] = $version; } } return $versions; } /** * Check if we should execute a migration for a given direction and target * migration version. * * @param string $direction The direction we are migrating. * @param Version $version The Version instance to check. * @param string $to The version we are migrating to. * @return void */ private function _shouldExecuteMigration($direction, Version $version, $to) { if ($direction === 'down') { if ( ! $this->hasVersionMigrated($version)) { return false; } return $version->getVersion() > $to ? true : false; } else if ($direction === 'up') { if ($this->hasVersionMigrated($version)) { return false; } return $version->getVersion() <= $to ? true : false; } } }. */ namespace Doctrine\DBAL\Migrations\Configuration; /** * Load migration configuration information from a XML configuration file. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class XmlConfiguration extends AbstractFileConfiguration { /** * @inheritdoc */ protected function _load($file) { $xml = simplexml_load_file($file); if (isset($xml->name)) { $this->setName((string) $xml->name); } if (isset($xml->table['name'])) { $this->setMigrationsTableName((string) $xml->table['name']); } if (isset($xml->{'migrations-namespace'})) { $this->setMigrationsNamespace((string) $xml->{'migrations-namespace'}); } if (isset($xml->{'migrations-directory'})) { $migrationsDirectory = $this->_getDirectoryRelativeToFile($file, (string) $xml->{'migrations-directory'}); $this->setMigrationsDirectory($migrationsDirectory); $this->registerMigrationsFromDirectory($migrationsDirectory); } if (isset($xml->migrations->migration)) { foreach ($xml->migrations->migration as $migration) { $this->registerMigration((string) $migration['version'], (string) $migration['class']); } } } }. */ namespace Doctrine\DBAL\Migrations\Configuration; use Symfony\Component\Yaml\Yaml; /** * Load migration configuration information from a YAML configuration file. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class YamlConfiguration extends AbstractFileConfiguration { /** * @inheritdoc */ protected function _load($file) { $array = Yaml::load($file); if (isset($array['name'])) { $this->setName($array['name']); } if (isset($array['table_name'])) { $this->setMigrationsTableName($array['table_name']); } if (isset($array['migrations_namespace'])) { $this->setMigrationsNamespace($array['migrations_namespace']); } if (isset($array['migrations_directory'])) { $migrationsDirectory = $this->_getDirectoryRelativeToFile($file, $array['migrations_directory']); $this->setMigrationsDirectory($migrationsDirectory); $this->registerMigrationsFromDirectory($migrationsDirectory); } if (isset($array['migrations']) && is_array($array['migrations'])) { foreach ($array['migrations'] as $migration) { $this->registerMigration($migration['version'], $migration['class']); } } } } . */ namespace Doctrine\DBAL\Migrations\Configuration; use Doctrine\DBAL\Migrations\MigrationsException; /** * Abstract Migration Configuration class for loading configuration information * from a configuration file (xml or yml). * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ abstract class AbstractFileConfiguration extends Configuration { /** The configuration file used to load configuration information */ private $_file; /** Whether or not the configuration file has been loaded yet or not */ private $_loaded = false; /** * Load the information from the passed configuration file * * @param string $file The path to the configuration file * @return void * @throws MigrationException $exception Throws exception if configuration file was already loaded */ public function load($file) { if ($this->_loaded) { throw MigrationsException::configurationFileAlreadyLoaded(); } if (file_exists($path = getcwd() . '/' . $file)) { $file = $path; } $this->_file = $file; $this->_load($file); $this->_loaded = true; } protected function _getDirectoryRelativeToFile($file, $input) { $path = realpath(dirname($file) . '/' . $input); if ($path !== false) { $directory = $path; } else { $directory = $input; } return $directory; } public function getFile() { return $this->_file; } /** * Abstract method that each file configuration driver must implement to * load the given configuration file whether it be xml, yaml, etc. or something * else. * * @param string $file The path to a configuration file. */ abstract protected function _load($file); }. */ namespace Doctrine\DBAL\Migrations; /** * Simple class for outputting information from migrations. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class OutputWriter { private $_closure; public function __construct(\Closure $closure = null) { if ($closure === null) { $closure = function($message) {}; } $this->_closure = $closure; } /** * Write output using the configured closure. * * @param string $message The message to write. */ public function write($message) { $closure = $this->_closure; $closure($message); } }. */ namespace Doctrine\DBAL\Migrations; use Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Schema\Schema; /** * Class which wraps a migration version and allows execution of the * individual migration version up or down method. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan H. Wage */ class Version { const STATE_NONE = 0; const STATE_PRE = 1; const STATE_EXEC = 2; const STATE_POST = 3; /** * The Migrations Configuration instance for this migration * * @var Configuration */ private $_configuration; /** * The OutputWriter object instance used for outputting information * * @var OutputWriter */ private $_outputWriter; /** * The version in timestamp format (YYYYMMDDHHMMSS) * * @param int */ private $_version; /** * @var AbstractSchemaManager */ private $_sm; /** * @var AbstractPlatform */ private $_platform; /** * The migration instance for this version * * @var AbstractMigration */ private $_migration; /** * @var Connection */ private $_connection; /** * @var string */ private $_class; /** The array of collected SQL statements for this version */ private $_sql = array(); /** The time in seconds that this migration version took to execute */ private $_time; /** * @var int */ private $_state = self::STATE_NONE; public function __construct(Configuration $configuration, $version, $class) { $this->_configuration = $configuration; $this->_outputWriter = $configuration->getOutputWriter(); $this->_version = $version; $this->_class = $class; $this->_connection = $configuration->getConnection(); $this->_sm = $this->_connection->getSchemaManager(); $this->_platform = $this->_connection->getDatabasePlatform(); $this->_migration = new $class($this); } /** * Returns the string version in the format YYYYMMDDHHMMSS * * @return string $version */ public function getVersion() { return $this->_version; } /** * Returns the Migrations Configuration object instance * * @return Configuration $configuration */ public function getConfiguration() { return $this->_configuration; } /** * Check if this version has been migrated or not. * * @param bool $bool * @return mixed */ public function isMigrated() { return $this->_configuration->hasVersionMigrated($this); } public function markMigrated() { $this->_configuration->createMigrationTable(); $this->_connection->executeQuery("INSERT INTO " . $this->_configuration->getMigrationsTableName() . " (version) VALUES (?)", array($this->_version)); } public function markNotMigrated() { $this->_configuration->createMigrationTable(); $this->_connection->executeQuery("DELETE FROM " . $this->_configuration->getMigrationsTableName() . " WHERE version = '$this->_version'"); } /** * Add some SQL queries to this versions migration * * @param mixed $sql * @return void */ public function addSql($sql) { if (is_array($sql)) { foreach ($sql as $query) { $this->_sql[] = $query; } } else { $this->_sql[] = $sql; } } /** * Write a migration SQL file to the given path * * @param string $path The path to write the migration SQL file. * @param string $direction The direction to execute. * @return bool $written */ public function writeSqlFile($path, $direction = 'up') { $queries = $this->execute($direction, true); $string = sprintf("# Doctrine Migration File Generated on %s\n", date('Y-m-d H:m:s')); $string .= "\n# Version " . $this->_version . "\n"; foreach ($queries as $query) { $string .= $query . ";\n"; } if (is_dir($path)) { $path = realpath($path); $path = $path . '/doctrine_migration_' . date('YmdHis') . '.sql'; } $this->_outputWriter->write("\n".sprintf('Writing migration file to "%s"', $path)); return file_put_contents($path, $string); } /** * Execute this migration version up or down and and return the SQL. * * @param string $direction The direction to execute the migration. * @param string $dryRun Whether to not actually execute the migration SQL and just do a dry run. * @return array $sql * @throws Exception when migration fails */ public function execute($direction, $dryRun = false) { $this->_sql = array(); $this->_connection->beginTransaction(); try { $start = microtime(true); $this->_state = self::STATE_PRE; $fromSchema = $this->_sm->createSchema(); $this->_migration->{'pre' . ucfirst($direction)}($fromSchema); if ($direction === 'up') { $this->_outputWriter->write("\n" . sprintf(' ++ migrating %s', $this->_version) . "\n"); } else { $this->_outputWriter->write("\n" . sprintf(' -- reverting %s', $this->_version) . "\n"); } $this->_state = self::STATE_EXEC; $toSchema = clone $fromSchema; $this->_migration->$direction($toSchema); $this->addSql($fromSchema->getMigrateToSql($toSchema, $this->_platform)); if ($dryRun === false) { if ($this->_sql) { $count = count($this->_sql); foreach ($this->_sql as $query) { $this->_outputWriter->write(' -> ' . $query); $this->_connection->executeQuery($query); } if ($direction === 'up') { $this->markMigrated(); } else { $this->markNotMigrated(); } } else { $this->_outputWriter->write(sprintf('Migration %s was executed but did not result in any SQL statements.', $this->_version)); } } else { foreach ($this->_sql as $query) { $this->_outputWriter->write(' -> ' . $query); } } $this->_state = self::STATE_POST; $this->_migration->{'post' . ucfirst($direction)}($toSchema); $end = microtime(true); $this->_time = round($end - $start, 2); if ($direction === 'up') { $this->_outputWriter->write(sprintf("\n ++ migrated (%ss)", $this->_time)); } else { $this->_outputWriter->write(sprintf("\n -- reverted (%ss)", $this->_time)); } $this->_connection->commit(); return $this->_sql; } catch(SkipMigrationException $e) { $this->_connection->rollback(); // now mark it as migrated if ($direction === 'up') { $this->markMigrated(); } else { $this->markNotMigrated(); } $this->_outputWriter->write(sprintf("\n SS skipped (Reason: %s)", $e->getMessage())); } catch (\Exception $e) { $this->_outputWriter->write(sprintf( 'Migration %s failed during %s. Error %s', $this->_version, $this->getExecutionState(), $e->getMessage() )); $this->_connection->rollback(); $this->_state = self::STATE_NONE; throw $e; } $this->_state = self::STATE_NONE; } public function getExecutionState() { switch($this->_state) { case self::STATE_PRE: return 'Pre-Checks'; case self::STATE_POST: return 'Post-Checks'; case self::STATE_EXEC: return 'Execution'; default: return 'No State'; } } /** * Returns the time this migration version took to execute * * @return integer $time The time this migration version took to execute */ public function getTime() { return $this->_time; } public function __toString() { return $this->_version; } } . */ namespace Doctrine\DBAL\Migrations; class MigrationsVersion { const VERSION = '2.0.0-DEV'; }. */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Doctrine\ORM\Tools\SchemaTool, Doctrine\DBAL\Migrations\Configuration\Configuration; /** * Command for generate migration classes by comparing your current database schema * to your mapping information. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ class DiffCommand extends GenerateCommand { protected function configure() { parent::configure(); $this ->setName('migrations:diff') ->setDescription('Generate a migration by comparing your current database to your mapping information.') ->setHelp(<<%command.name% command generates a migration by comparing your current database to your mapping information: %command.full_name% You can optionally specify a --editor-cmd option to open the generated file in your favorite editor: %command.full_name% --editor-cmd=mate EOT ); } public function execute(InputInterface $input, OutputInterface $output) { $configuration = $this->_getMigrationConfiguration($input, $output); $em = $this->getHelper('em')->getEntityManager(); $conn = $em->getConnection(); $platform = $conn->getDatabasePlatform(); $metadata = $em->getMetadataFactory()->getAllMetadata(); if (empty($metadata)) { $output->writeln('No mapping information to process.', 'ERROR'); return; } $tool = new SchemaTool($em); $fromSchema = $conn->getSchemaManager()->createSchema(); $toSchema = $tool->getSchemaFromMetadata($metadata); $up = $this->_buildCodeFromSql($configuration, $fromSchema->getMigrateToSql($toSchema, $platform)); $down = $this->_buildCodeFromSql($configuration, $fromSchema->getMigrateFromSql($toSchema, $platform)); if ( ! $up && ! $down) { $output->writeln('No changes detected in your mapping information.', 'ERROR'); return; } $version = date('YmdHis'); $path = $this->_generateMigration($configuration, $input, $version, $up, $down); $output->writeln(sprintf('Generated new migration class to "%s" from schema differences.', $path)); } private function _buildCodeFromSql(Configuration $configuration, array $sql) { $code = array(); foreach ($sql as $query) { if (strpos($query, $configuration->getMigrationsTableName()) !== false) { continue; } $code[] = "\$this->_addSql('" . $query . "');"; } return implode("\n", $code); } } . */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Command\Command, Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputOption, Doctrine\DBAL\Migrations\Migration, Doctrine\DBAL\Migrations\MigrationException, Doctrine\DBAL\Migrations\OutputWriter, Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Migrations\Configuration\YamlConfiguration, Doctrine\DBAL\Migrations\Configuration\XmlConfiguration; /** * CLI Command for adding and deleting migration versions from the version table. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ abstract class AbstractCommand extends Command { protected $_configuration; protected function configure() { $this->addOption('configuration', null, InputOption::PARAMETER_OPTIONAL, 'The path to a migrations configuration file.'); $this->addOption('db-configuration', null, InputOption::PARAMETER_OPTIONAL, 'The path to a database connection configuration file.'); } protected function _outputHeader(Configuration $configuration, OutputInterface $output) { $name = $configuration->getName(); $name = $name ? $name : 'Doctrine Database Migrations'; $name = str_repeat(' ', 20) . $name . str_repeat(' ', 20); $output->writeln('' . str_repeat(' ', strlen($name)) . ''); $output->writeln('' . $name . ''); $output->writeln('' . str_repeat(' ', strlen($name)) . ''); $output->writeln(''); } /** * @param InputInterface $input * @param OutputInterface $output * @return Configuration */ protected function _getMigrationConfiguration(InputInterface $input, OutputInterface $output) { if ( ! $this->_configuration) { $outputWriter = new OutputWriter(function($message) use ($output) { return $output->writeln($message); }); if ($this->application->getHelperSet()->has('db')) { $conn = $this->getHelper('db')->getConnection(); } else if($input->getOption('db-configuration')) { if (!file_exists($input->getOption('db-configuration'))) { throw new \InvalidArgumentException("The specified connection file is a valid file."); } $params = include($input->getOption('db-configuration')); if (!is_array($params)) { throw new \InvalidArgumentException('The connection file has to return an array with database configuration parameters.'); } $conn = \Doctrine\DBAL\DriverManager::getConnection($params); } else if (file_exists('migrations-db.php')) { $params = include("migrations-db.php"); if (!is_array($params)) { throw new \InvalidArgumentException('The connection file has to return an array with database configuration parameters.'); } $conn = \Doctrine\DBAL\DriverManager::getConnection($params); } else { throw new \InvalidArgumentException('You have to specify a --db-configuration file or pass a Database Connection as a dependency to the Migrations.'); } if ($input->getOption('configuration')) { $info = pathinfo($input->getOption('configuration')); $class = $info['extension'] === 'xml' ? 'Doctrine\DBAL\Migrations\Configuration\XmlConfiguration' : 'Doctrine\DBAL\Migrations\Configuration\YamlConfiguration'; $configuration = new $class($conn, $outputWriter); $configuration->load($input->getOption('configuration')); } else if (file_exists('migrations.xml')) { $configuration = new XmlConfiguration($conn, $outputWriter); $configuration->load('migrations.xml'); } else if (file_exists('migrations.yml')) { $configuration = new YamlConfiguration($conn, $outputWriter); $configuration->load('migrations.yml'); } else { $configuration = new Configuration($conn, $outputWriter); } $this->_configuration = $configuration; } return $this->_configuration; } } . */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Doctrine\DBAL\Migrations\MigrationException, Doctrine\DBAL\Migrations\Configuration\Configuration; /** * Command for generating new blank migration classes * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ class GenerateCommand extends AbstractCommand { private static $_template = '; use Doctrine\DBAL\Migrations\AbstractMigration, Doctrine\DBAL\Schema\Schema; class Version extends AbstractMigration { public function up(Schema $schema) { } public function down(Schema $schema) { } }'; protected function configure() { $this ->setName('migrations:generate') ->setDescription('Generate a blank migration class.') ->addOption('editor-cmd', null, InputOption::PARAMETER_OPTIONAL, 'Open file with this command upon creation.') ->setHelp(<<%command.name% command generates a blank migration class: %command.full_name% You can optionally specify a --editor-cmd option to open the generated file in your favorite editor: %command.full_name% --editor-cmd=mate EOT ); parent::configure(); } public function execute(InputInterface $input, OutputInterface $output) { $configuration = $this->_getMigrationConfiguration($input, $output); $version = date('YmdHis'); $path = $this->_generateMigration($configuration, $input, $version); $output->writeln(sprintf('Generated new migration class to "%s"', $path)); } protected function _generateMigration(Configuration $configuration, InputInterface $input, $version, $up = null, $down = null) { $placeHolders = array( '', '', '', '' ); $replacements = array( $configuration->getMigrationsNamespace(), $version, $up ? " " . implode("\n ", explode("\n", $up)) : null, $down ? " " . implode("\n ", explode("\n", $down)) : null ); $code = str_replace($placeHolders, $replacements, self::$_template); $dir = $configuration->getMigrationsDirectory(); $dir = $dir ? $dir : getcwd(); $dir = rtrim($dir, '/'); $path = $dir . '/Version' . $version . '.php'; file_put_contents($path, $code); if ($editorCmd = $input->getOption('editor-cmd')) { shell_exec($editorCmd . ' ' . escapeshellarg($path)); } return $path; } } . */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Doctrine\DBAL\Migrations\Migration, Doctrine\DBAL\Migrations\MigrationException, Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Migrations\Configuration\YamlConfiguration, Doctrine\DBAL\Migrations\Configuration\XmlConfiguration; /** * Command for manually adding and deleting migration versions from the version table. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ class VersionCommand extends AbstractCommand { protected function configure() { $this ->setName('migrations:version') ->setDescription('Manually add and delete migration versions from the version table.') ->addArgument('version', InputArgument::REQUIRED, 'The version to add or delete.', null) ->addOption('add', null, InputOption::PARAMETER_NONE, 'Add the specified version.') ->addOption('delete', null, InputOption::PARAMETER_NONE, 'Delete the specified version.') ->setHelp(<<%command.name% command allows you to manually add and delete migration versions from the version table: %command.full_name% YYYYMMDDHHMMSS --add If you want to delete a version you can use the --delete option: %command.full_name% YYYYMMDDHHMMSS --delete EOT ); parent::configure(); } public function execute(InputInterface $input, OutputInterface $output) { $configuration = $this->_getMigrationConfiguration($input, $output); $migration = new Migration($configuration); if ($input->getOption('add') === false && $input->getOption('delete') === false) { throw new \InvalidArgumentException('You must specify whether you want to --add or --delete the specified version.'); } $version = $input->getArgument('version'); $markMigrated = $input->getOption('add') ? true : false; if ( ! $configuration->hasVersion($version)) { throw MigrationException::unknownMigrationVersion($version); } $version = $configuration->getVersion($version); if ($markMigrated && $configuration->hasVersionMigrated($version)) { throw new \InvalidArgumentException(sprintf('The version "%s" already exists in the version table.', $version)); } if ( ! $markMigrated && ! $configuration->hasVersionMigrated($version)) { throw new \InvalidArgumentException(sprintf('The version "%s" does not exists in the version table.', $version)); } if ($markMigrated) { $version->markMigrated(); } else { $version->markNotMigrated(); } } } . */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Doctrine\DBAL\Migrations\Migration, Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Migrations\Configuration\YamlConfiguration, Doctrine\DBAL\Migrations\Configuration\XmlConfiguration; /** * Command for executing single migrations up or down manually. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ class ExecuteCommand extends AbstractCommand { protected function configure() { $this ->setName('migrations:execute') ->setDescription('Execute a single migration version up or down manually.') ->addArgument('version', InputArgument::REQUIRED, 'The version to execute.', null) ->addOption('write-sql', null, InputOption::PARAMETER_NONE, 'The path to output the migration SQL file instead of executing it.') ->addOption('dry-run', null, InputOption::PARAMETER_NONE, 'Execute the migration as a dry run.') ->addOption('up', null, InputOption::PARAMETER_NONE, 'Execute the migration down.') ->addOption('down', null, InputOption::PARAMETER_NONE, 'Execute the migration down.') ->setHelp(<<%command.name% command executes a single migration version up or down manually: %command.full_name% YYYYMMDDHHMMSS If no --up or --down option is specified it defaults to up: %command.full_name% YYYYMMDDHHMMSS --down You can also execute the migration as a --dry-run: %command.full_name% YYYYMMDDHHMMSS --dry-run Or you can output the would be executed SQL statements to a file with --write-sql: %command.full_name% YYYYMMDDHHMMSS --write-sql EOT ); parent::configure(); } public function execute(InputInterface $input, OutputInterface $output) { $version = $input->getArgument('version'); $direction = $input->getOption('down') ? 'down' : 'up'; $configuration = $this->_getMigrationConfiguration($input, $output); $version = $configuration->getVersion($version); if ($path = $input->getOption('write-sql')) { $path = is_bool($path) ? getcwd() : $path; $version->writeSqlFile($path, $direction); } else { $confirmation = $this->getHelper('dialog')->askConfirmation($output, 'WARNING! You are about to execute a database migration that could result in schema changes and data lost. Are you sure you wish to continue? (y/n)', 'y'); if ($confirmation === true) { $version->execute($direction, $input->getOption('dry-run') ? true : false); } else { $output->writeln('Migration cancelled!'); } } } } . */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Doctrine\DBAL\Migrations\Migration, Doctrine\DBAL\Migrations\MigrationException, Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Migrations\Configuration\YamlConfiguration, Doctrine\DBAL\Migrations\Configuration\XmlConfiguration; /** * Command to view the status of a set of migrations. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ class StatusCommand extends AbstractCommand { protected function configure() { $this ->setName('migrations:status') ->setDescription('View the status of a set of migrations.') ->addOption('show-versions', null, InputOption::PARAMETER_NONE, 'This will display a list of all available migrations and their status') ->setHelp(<<%command.name% command outputs the status of a set of migrations: %command.full_name% You can output a list of all available migrations and their status with --show-versions: %command.full_name% --show-versions EOT ); parent::configure(); } public function execute(InputInterface $input, OutputInterface $output) { $configuration = $this->_getMigrationConfiguration($input, $output); $currentVersion = $configuration->getCurrentVersion(); if ($currentVersion) { $currentVersionFormatted = $configuration->formatVersion($currentVersion) . ' ('.$currentVersion.')'; } else { $currentVersionFormatted = 0; } $latestVersion = $configuration->getLatestVersion(); if ($latestVersion) { $latestVersionFormatted = $configuration->formatVersion($latestVersion) . ' ('.$latestVersion.')'; } else { $latestVersionFormatted = 0; } $executedMigrations = $configuration->getNumberOfExecutedMigrations(); $availableMigrations = $configuration->getNumberOfAvailableMigrations(); $newMigrations = $availableMigrations - $executedMigrations; $output->writeln("\n == Configuration\n"); $info = array( 'Name' => $configuration->getName() ? $configuration->getName() : 'Doctrine Database Migrations', 'Database Driver' => $configuration->getConnection()->getDriver()->getName(), 'Database Name' => $configuration->getConnection()->getDatabase(), 'Configuration Source' => $configuration instanceof \Doctrine\DBAL\Migrations\Configuration\AbstractFileConfiguration ? $configuration->getFile() : 'manually configured', 'Version Table Name' => $configuration->getMigrationsTableName(), 'Migrations Namespace' => $configuration->getMigrationsNamespace(), 'Migrations Directory' => $configuration->getMigrationsDirectory(), 'Current Version' => $currentVersionFormatted, 'Latest Version' => $latestVersionFormatted, 'Executed Migrations' => $executedMigrations, 'Available Migrations' => $availableMigrations, 'New Migrations' => $newMigrations > 0 ? '' . $newMigrations . '' : $newMigrations ); foreach ($info as $name => $value) { $output->writeln(' >> ' . $name . ': ' . str_repeat(' ', 50 - strlen($name)) . $value); } $showVersions = $input->getOption('show-versions') ? true : false; if ($showVersions === true) { if ($migrations = $configuration->getMigrations()) { $output->writeln("\n == Migration Versions\n"); foreach ($migrations as $version) { $isMigrated = $version->isMigrated(); $status = $isMigrated ? 'migrated' : 'not migrated'; $output->writeln(' >> ' . $configuration->formatVersion($version->getVersion()) . ' (' . $version->getVersion() . ')' . str_repeat(' ', 30 - strlen($name)) . $status); } } } } } . */ namespace Doctrine\DBAL\Migrations\Tools\Console\Command; use Symfony\Component\Console\Input\InputInterface, Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Doctrine\DBAL\Migrations\Migration, Doctrine\DBAL\Migrations\Configuration\Configuration, Doctrine\DBAL\Migrations\Configuration\YamlConfiguration, Doctrine\DBAL\Migrations\Configuration\XmlConfiguration; /** * Command for executing a migration to a specified version or the latest available version. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Jonathan Wage */ class MigrateCommand extends AbstractCommand { protected function configure() { $this ->setName('migrations:migrate') ->setDescription('Execute a migration to a specified version or the latest available version.') ->addArgument('version', InputArgument::OPTIONAL, 'The version to migrate to.', null) ->addOption('write-sql', null, InputOption::PARAMETER_NONE, 'The path to output the migration SQL file instead of executing it.') ->addOption('dry-run', null, InputOption::PARAMETER_NONE, 'Execute the migration as a dry run.') ->setHelp(<<%command.name% command executes a migration to a specified version or the latest available version: %command.full_name% You can optionally manually specify the version you wish to migrate to: %command.full_name% YYYYMMDDHHMMSS You can also execute the migration as a --dry-run: %command.full_name% YYYYMMDDHHMMSS --dry-run Or you can output the would be executed SQL statements to a file with --write-sql: %command.full_name% YYYYMMDDHHMMSS --write-sql You can also execute the migration without a warning message wich you need to interact with: %command.full_name% --no-interaction EOT ); parent::configure(); } public function execute(InputInterface $input, OutputInterface $output) { $version = $input->getArgument('version'); $configuration = $this->_getMigrationConfiguration($input, $output); $migration = new Migration($configuration); $this->_outputHeader($configuration, $output); if ($path = $input->getOption('write-sql')) { $path = is_bool($path) ? getcwd() : $path; $migration->writeSqlFile($path, $version); } else { $dryRun = $input->getOption('dry-run') ? true : false; if ($dryRun === true) { $migration->migrate($version, true); } else { $noInteraction = $input->getOption('no-interaction') ? true : false; if ($noInteraction === true) { $migration->migrate($version, $dryRun); } else { $confirmation = $this->getHelper('dialog')->askConfirmation($output, 'WARNING! You are about to execute a database migration that could result in schema changes and data lost. Are you sure you wish to continue? (y/n)', 'y'); if ($confirmation === true) { $migration->migrate($version, $dryRun); } else { $output->writeln('Migration cancelled!'); } } } } } } . */ namespace Doctrine\DBAL\Driver\PDOOracle; use Doctrine\DBAL\Platforms; class Driver implements \Doctrine\DBAL\Driver { public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { return new \Doctrine\DBAL\Driver\PDOConnection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); } /** * Constructs the Oracle PDO DSN. * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'oci:'; if (isset($params['host'])) { $dsn .= 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' . '(HOST=' . $params['host'] . ')'; if (isset($params['port'])) { $dsn .= '(PORT=' . $params['port'] . ')'; } else { $dsn .= '(PORT=1521)'; } $dsn .= '))(CONNECT_DATA=(SID=' . $params['dbname'] . ')))'; } else { $dsn .= 'dbname=' . $params['dbname']; } if (isset($params['charset'])) { $dsn .= ';charset=' . $params['charset']; } return $dsn; } public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\OraclePlatform(); } public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\OracleSchemaManager($conn); } public function getName() { return 'pdo_oracle'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['user']; } }. */ namespace Doctrine\DBAL\Driver\IBMDB2; class DB2Connection implements \Doctrine\DBAL\Driver\Connection { private $_conn = null; public function __construct(array $params, $username, $password, $driverOptions = array()) { $isPersistant = (isset($params['persistent']) && $params['persistent'] == true); if ($isPersistant) { $this->_conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions); } else { $this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions); } if (!$this->_conn) { throw new DB2Exception(db2_conn_errormsg()); } } function prepare($sql) { $stmt = @db2_prepare($this->_conn, $sql); if (!$stmt) { throw new DB2Exception(db2_stmt_errormsg()); } return new DB2Statement($stmt); } function query() { $args = func_get_args(); $sql = $args[0]; $stmt = $this->prepare($sql); $stmt->execute(); return $stmt; } function quote($input, $type=\PDO::PARAM_STR) { $input = db2_escape_string($input); if ($type == \PDO::PARAM_INT ) { return $input; } else { return "'".$input."'"; } } function exec($statement) { $stmt = $this->prepare($statement); $stmt->execute(); return $stmt->rowCount(); } function lastInsertId($name = null) { return db2_last_insert_id($this->_conn); } function beginTransaction() { db2_autocommit($this->_conn, DB2_AUTOCOMMIT_OFF); } function commit() { if (!db2_commit($this->_conn)) { throw new DB2Exception(db2_conn_errormsg($this->_conn)); } db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); } function rollBack() { if (!db2_rollback($this->_conn)) { throw new DB2Exception(db2_conn_errormsg($this->_conn)); } db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); } function errorCode() { return db2_conn_error($this->_conn); } function errorInfo() { return array( 0 => db2_conn_errormsg($this->_conn), 1 => $this->errorCode(), ); } }. */ namespace Doctrine\DBAL\Driver\IBMDB2; class DB2Statement implements \Doctrine\DBAL\Driver\Statement { private $_stmt = null; private $_bindParam = array(); /** * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG * @var */ static private $_typeMap = array( \PDO::PARAM_INT => DB2_LONG, \PDO::PARAM_STR => DB2_CHAR, ); public function __construct($stmt) { $this->_stmt = $stmt; } /** * Binds a value to a corresponding named or positional * placeholder in the SQL statement that was used to prepare the statement. * * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter * * @param mixed $value The value to bind to the parameter. * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. * * @return boolean Returns TRUE on success or FALSE on failure. */ function bindValue($param, $value, $type = null) { return $this->bindParam($param, $value, $type); } /** * Binds a PHP variable to a corresponding named or question mark placeholder in the * SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(), * the variable is bound as a reference and will only be evaluated at the time * that PDOStatement->execute() is called. * * Most parameters are input parameters, that is, parameters that are * used in a read-only fashion to build up the query. Some drivers support the invocation * of stored procedures that return data as output parameters, and some also as input/output * parameters that both send in data and are updated to receive it. * * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter * * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. * @return boolean Returns TRUE on success or FALSE on failure. */ function bindParam($column, &$variable, $type = null) { $this->_bindParam[$column] =& $variable; if ($type && isset(self::$_typeMap[$type])) { $type = self::$_typeMap[$type]; } else { $type = DB2_CHAR; } if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) { throw new DB2Exception(db2_stmt_errormsg()); } return true; } /** * Closes the cursor, enabling the statement to be executed again. * * @return boolean Returns TRUE on success or FALSE on failure. */ function closeCursor() { if (!$this->_stmt) { return false; } $this->_bindParam = array(); db2_free_result($this->_stmt); $ret = db2_free_stmt($this->_stmt); $this->_stmt = false; return $ret; } /** * columnCount * Returns the number of columns in the result set * * @return integer Returns the number of columns in the result set represented * by the PDOStatement object. If there is no result set, * this method should return 0. */ function columnCount() { if (!$this->_stmt) { return false; } return db2_num_fields($this->_stmt); } /** * errorCode * Fetch the SQLSTATE associated with the last operation on the statement handle * * @see Doctrine_Adapter_Interface::errorCode() * @return string error code string */ function errorCode() { return db2_stmt_error(); } /** * errorInfo * Fetch extended error information associated with the last operation on the statement handle * * @see Doctrine_Adapter_Interface::errorInfo() * @return array error info array */ function errorInfo() { return array( 0 => db2_stmt_errormsg(), 1 => db2_stmt_error(), ); } /** * Executes a prepared statement * * If the prepared statement included parameter markers, you must either: * call PDOStatement->bindParam() to bind PHP variables to the parameter markers: * bound variables pass their value as input and receive the output value, * if any, of their associated parameter markers or pass an array of input-only * parameter values * * * @param array $params An array of values with as many elements as there are * bound parameters in the SQL statement being executed. * @return boolean Returns TRUE on success or FALSE on failure. */ function execute($params = null) { if (!$this->_stmt) { return false; } /*$retval = true; if ($params !== null) { $retval = @db2_execute($this->_stmt, $params); } else { $retval = @db2_execute($this->_stmt); }*/ if ($params === null) { ksort($this->_bindParam); $params = array_values($this->_bindParam); } $retval = @db2_execute($this->_stmt, $params); if ($retval === false) { throw new DB2Exception(db2_stmt_errormsg()); } return $retval; } /** * fetch * * @see Query::HYDRATE_* constants * @param integer $fetchStyle Controls how the next row will be returned to the caller. * This value must be one of the Query::HYDRATE_* constants, * defaulting to Query::HYDRATE_BOTH * * @param integer $cursorOrientation For a PDOStatement object representing a scrollable cursor, * this value determines which row will be returned to the caller. * This value must be one of the Query::HYDRATE_ORI_* constants, defaulting to * Query::HYDRATE_ORI_NEXT. To request a scrollable cursor for your * PDOStatement object, * you must set the PDO::ATTR_CURSOR attribute to Doctrine::CURSOR_SCROLL when you * prepare the SQL statement with Doctrine_Adapter_Interface->prepare(). * * @param integer $cursorOffset For a PDOStatement object representing a scrollable cursor for which the * $cursorOrientation parameter is set to Query::HYDRATE_ORI_ABS, this value specifies * the absolute number of the row in the result set that shall be fetched. * * For a PDOStatement object representing a scrollable cursor for * which the $cursorOrientation parameter is set to Query::HYDRATE_ORI_REL, this value * specifies the row to fetch relative to the cursor position before * PDOStatement->fetch() was called. * * @return mixed */ function fetch($fetchStyle = \PDO::FETCH_BOTH) { switch ($fetchStyle) { case \PDO::FETCH_BOTH: return db2_fetch_both($this->_stmt); case \PDO::FETCH_ASSOC: return db2_fetch_assoc($this->_stmt); case \PDO::FETCH_NUM: return db2_fetch_array($this->_stmt); default: throw new DB2Exception("Given Fetch-Style " . $fetchStyle . " is not supported."); } } /** * Returns an array containing all of the result set rows * * @param integer $fetchStyle Controls how the next row will be returned to the caller. * This value must be one of the Query::HYDRATE_* constants, * defaulting to Query::HYDRATE_BOTH * * @param integer $columnIndex Returns the indicated 0-indexed column when the value of $fetchStyle is * Query::HYDRATE_COLUMN. Defaults to 0. * * @return array */ function fetchAll($fetchStyle = \PDO::FETCH_BOTH) { $rows = array(); while ($row = $this->fetch($fetchStyle)) { $rows[] = $row; } return $rows; } /** * fetchColumn * Returns a single column from the next row of a * result set or FALSE if there are no more rows. * * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. If no * value is supplied, PDOStatement->fetchColumn() * fetches the first column. * * @return string returns a single column in the next row of a result set. */ function fetchColumn($columnIndex = 0) { $row = $this->fetch(\PDO::FETCH_NUM); if ($row && isset($row[$columnIndex])) { return $row[$columnIndex]; } return false; } /** * rowCount * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement * executed by the corresponding object. * * If the last SQL statement executed by the associated Statement object was a SELECT statement, * some databases may return the number of rows returned by that statement. However, * this behaviour is not guaranteed for all databases and should not be * relied on for portable applications. * * @return integer Returns the number of rows. */ function rowCount() { return (@db2_num_rows($this->_stmt))?:0; } } . */ namespace Doctrine\DBAL\Driver\IBMDB2; class DB2Exception extends \Exception { }. */ namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver, Doctrine\DBAL\Connection; /** * IBM DB2 Driver * * @since 2.0 * @author Benjamin Eberlei */ class DB2Driver implements Driver { /** * Attempts to create a connection with the database. * * @param array $params All connection parameters passed by the user. * @param string $username The username to use when connecting. * @param string $password The password to use when connecting. * @param array $driverOptions The driver options to use when connecting. * @return Doctrine\DBAL\Driver\Connection The database connection. */ public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { if ( !isset($params['schema']) ) { } if ($params['host'] !== 'localhost' && $params['host'] != '127.0.0.1') { // if the host isn't localhost, use extended connection params $params['dbname'] = 'DRIVER={IBM DB2 ODBC DRIVER}' . ';DATABASE=' . $params['dbname'] . ';HOSTNAME=' . $params['host'] . ';PORT=' . $params['port'] . ';PROTOCOL=' . $params['protocol'] . ';UID=' . $username . ';PWD=' . $password .';'; $username = null; $password = null; } return new DB2Connection($params, $username, $password, $driverOptions); } /** * Gets the DatabasePlatform instance that provides all the metadata about * the platform this driver connects to. * * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform. */ public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\DB2Platform; } /** * Gets the SchemaManager that can be used to inspect and change the underlying * database schema of the platform this driver connects to. * * @param Doctrine\DBAL\Connection $conn * @return Doctrine\DBAL\SchemaManager */ public function getSchemaManager(Connection $conn) { return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn); } /** * Gets the name of the driver. * * @return string The name of the driver. */ public function getName() { return 'ibm_db2'; } /** * Get the name of the database connected to for this driver. * * @param Doctrine\DBAL\Connection $conn * @return string $database */ public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } } . */ namespace Doctrine\DBAL\Driver; /** * The PDO implementation of the Statement interface. * Used by all PDO-based drivers. * * @since 2.0 */ class PDOStatement extends \PDOStatement implements Statement { private function __construct() {} }. */ namespace Doctrine\DBAL\Driver\PDOMsSql; /** * The PDO-based MsSql driver. * * @since 2.0 */ class Driver implements \Doctrine\DBAL\Driver { public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { return new Connection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); } /** * Constructs the MsSql PDO DSN. * * @return string The DSN. */ private function _constructPdoDsn(array $params) { // TODO: This might need to be revisted once we have access to a mssql server $dsn = 'mssql:'; if (isset($params['host'])) { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } return $dsn; } public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\MsSqlPlatform(); } public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\MsSqlSchemaManager($conn); } public function getName() { return 'pdo_mssql'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } }. */ namespace Doctrine\DBAL\Driver\PDOMsSql; /** * MsSql Connection implementation. * * @since 2.0 */ class Connection extends \PDO implements \Doctrine\DBAL\Driver\Connection { /** * Performs the rollback. * * @override */ public function rollback() { $this->exec('ROLLBACK TRANSACTION'); } /** * Performs the commit. * * @override */ public function commit() { $this->exec('COMMIT TRANSACTION'); } /** * Begins a database transaction. * * @override */ public function beginTransaction() { $this->exec('BEGIN TRANSACTION'); } }. */ namespace Doctrine\DBAL\Driver; use \PDO; /** * Statement interface. * Drivers must implement this interface. * * This resembles (a subset of) the PDOStatement interface. * * @author Konsta Vesterinen * @author Roman Borschel * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ */ interface Statement { /** * Binds a value to a corresponding named or positional * placeholder in the SQL statement that was used to prepare the statement. * * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter * * @param mixed $value The value to bind to the parameter. * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. * * @return boolean Returns TRUE on success or FALSE on failure. */ function bindValue($param, $value, $type = null); /** * Binds a PHP variable to a corresponding named or question mark placeholder in the * SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(), * the variable is bound as a reference and will only be evaluated at the time * that PDOStatement->execute() is called. * * Most parameters are input parameters, that is, parameters that are * used in a read-only fashion to build up the query. Some drivers support the invocation * of stored procedures that return data as output parameters, and some also as input/output * parameters that both send in data and are updated to receive it. * * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, * this will be a parameter name of the form :name. For a prepared statement * using question mark placeholders, this will be the 1-indexed position of the parameter * * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. * * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. * @return boolean Returns TRUE on success or FALSE on failure. */ function bindParam($column, &$variable, $type = null); /** * Closes the cursor, enabling the statement to be executed again. * * @return boolean Returns TRUE on success or FALSE on failure. */ function closeCursor(); /** * columnCount * Returns the number of columns in the result set * * @return integer Returns the number of columns in the result set represented * by the PDOStatement object. If there is no result set, * this method should return 0. */ function columnCount(); /** * errorCode * Fetch the SQLSTATE associated with the last operation on the statement handle * * @see Doctrine_Adapter_Interface::errorCode() * @return string error code string */ function errorCode(); /** * errorInfo * Fetch extended error information associated with the last operation on the statement handle * * @see Doctrine_Adapter_Interface::errorInfo() * @return array error info array */ function errorInfo(); /** * Executes a prepared statement * * If the prepared statement included parameter markers, you must either: * call PDOStatement->bindParam() to bind PHP variables to the parameter markers: * bound variables pass their value as input and receive the output value, * if any, of their associated parameter markers or pass an array of input-only * parameter values * * * @param array $params An array of values with as many elements as there are * bound parameters in the SQL statement being executed. * @return boolean Returns TRUE on success or FALSE on failure. */ function execute($params = null); /** * fetch * * @see Query::HYDRATE_* constants * @param integer $fetchStyle Controls how the next row will be returned to the caller. * This value must be one of the Query::HYDRATE_* constants, * defaulting to Query::HYDRATE_BOTH * * @param integer $cursorOrientation For a PDOStatement object representing a scrollable cursor, * this value determines which row will be returned to the caller. * This value must be one of the Query::HYDRATE_ORI_* constants, defaulting to * Query::HYDRATE_ORI_NEXT. To request a scrollable cursor for your * PDOStatement object, * you must set the PDO::ATTR_CURSOR attribute to Doctrine::CURSOR_SCROLL when you * prepare the SQL statement with Doctrine_Adapter_Interface->prepare(). * * @param integer $cursorOffset For a PDOStatement object representing a scrollable cursor for which the * $cursorOrientation parameter is set to Query::HYDRATE_ORI_ABS, this value specifies * the absolute number of the row in the result set that shall be fetched. * * For a PDOStatement object representing a scrollable cursor for * which the $cursorOrientation parameter is set to Query::HYDRATE_ORI_REL, this value * specifies the row to fetch relative to the cursor position before * PDOStatement->fetch() was called. * * @return mixed */ function fetch($fetchStyle = PDO::FETCH_BOTH); /** * Returns an array containing all of the result set rows * * @param integer $fetchStyle Controls how the next row will be returned to the caller. * This value must be one of the Query::HYDRATE_* constants, * defaulting to Query::HYDRATE_BOTH * * @param integer $columnIndex Returns the indicated 0-indexed column when the value of $fetchStyle is * Query::HYDRATE_COLUMN. Defaults to 0. * * @return array */ function fetchAll($fetchStyle = PDO::FETCH_BOTH); /** * fetchColumn * Returns a single column from the next row of a * result set or FALSE if there are no more rows. * * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. If no * value is supplied, PDOStatement->fetchColumn() * fetches the first column. * * @return string returns a single column in the next row of a result set. */ function fetchColumn($columnIndex = 0); /** * rowCount * rowCount() returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement * executed by the corresponding object. * * If the last SQL statement executed by the associated Statement object was a SELECT statement, * some databases may return the number of rows returned by that statement. However, * this behaviour is not guaranteed for all databases and should not be * relied on for portable applications. * * @return integer Returns the number of rows. */ function rowCount(); }_constructPdoDsn($params), $username, $password, $driverOptions ); } /** * Constructs the Postgres PDO DSN. * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'pgsql:'; if (isset($params['host'])) { $dsn .= 'host=' . $params['host'] . ' '; } if (isset($params['port'])) { $dsn .= 'port=' . $params['port'] . ' '; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ' '; } return $dsn; } public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\PostgreSqlPlatform(); } public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\PostgreSqlSchemaManager($conn); } public function getName() { return 'pdo_pgsql'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } }. */ namespace Doctrine\DBAL\Driver; use \PDO; /** * PDO implementation of the Connection interface. * Used by all PDO-based drivers. * * @since 2.0 */ class PDOConnection extends PDO implements Connection { public function __construct($dsn, $user = null, $password = null, array $options = null) { parent::__construct($dsn, $user, $password, $options); $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Doctrine\DBAL\Driver\PDOStatement', array())); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } }. */ namespace Doctrine\DBAL\Driver\PDOIbm; use Doctrine\DBAL\Connection; /** * Driver for the PDO IBM extension * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Driver implements \Doctrine\DBAL\Driver { /** * Attempts to establish a connection with the underlying driver. * * @param array $params * @param string $username * @param string $password * @param array $driverOptions * @return Doctrine\DBAL\Driver\Connection */ public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { $conn = new \Doctrine\DBAL\Driver\PDOConnection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); return $conn; } /** * Constructs the MySql PDO DSN. * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'ibm:'; if (isset($params['host'])) { $dsn .= 'HOSTNAME=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'PORT=' . $params['port'] . ';'; } $dsn .= 'PROTOCOL=TCPIP;'; if (isset($params['dbname'])) { $dsn .= 'DATABASE=' . $params['dbname'] . ';'; } return $dsn; } /** * Gets the DatabasePlatform instance that provides all the metadata about * the platform this driver connects to. * * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform. */ public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\DB2Platform; } /** * Gets the SchemaManager that can be used to inspect and change the underlying * database schema of the platform this driver connects to. * * @param Doctrine\DBAL\Connection $conn * @return Doctrine\DBAL\SchemaManager */ public function getSchemaManager(Connection $conn) { return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn); } /** * Gets the name of the driver. * * @return string The name of the driver. */ public function getName() { return 'pdo_ibm'; } /** * Get the name of the database connected to for this driver. * * @param Doctrine\DBAL\Connection $conn * @return string $database */ public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } }. */ namespace Doctrine\DBAL\Driver\PDOMySql; use Doctrine\DBAL\Connection; /** * PDO MySql driver. * * @since 2.0 */ class Driver implements \Doctrine\DBAL\Driver { /** * Attempts to establish a connection with the underlying driver. * * @param array $params * @param string $username * @param string $password * @param array $driverOptions * @return Doctrine\DBAL\Driver\Connection */ public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { $conn = new \Doctrine\DBAL\Driver\PDOConnection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); return $conn; } /** * Constructs the MySql PDO DSN. * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'mysql:'; if (isset($params['host'])) { $dsn .= 'host=' . $params['host'] . ';'; } if (isset($params['port'])) { $dsn .= 'port=' . $params['port'] . ';'; } if (isset($params['dbname'])) { $dsn .= 'dbname=' . $params['dbname'] . ';'; } if (isset($params['unix_socket'])) { $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; } return $dsn; } public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\MySqlPlatform(); } public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\MySqlSchemaManager($conn); } public function getName() { return 'pdo_mysql'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } }. */ namespace Doctrine\DBAL\Driver\PDOSqlsrv; /** * The PDO-based Sqlsrv driver. * * @since 2.0 */ class Driver implements \Doctrine\DBAL\Driver { public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { return new \Doctrine\DBAL\Driver\PDOConnection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); } /** * Constructs the Sqlsrv PDO DSN. * * @return string The DSN. */ private function _constructPdoDsn(array $params) { $dsn = 'sqlsrv:server='; if (isset($params['host'])) { $dsn .= $params['host']; } if (isset($params['port']) && !empty($params['port'])) { $dsn .= ',' . $params['port']; } if (isset($params['dbname'])) { $dsn .= ';Database=' . $params['dbname']; } return $dsn; } public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\MsSqlPlatform(); } public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\MsSqlSchemaManager($conn); } public function getName() { return 'pdo_sqlsrv'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['dbname']; } }. */ namespace Doctrine\DBAL\Driver\PDOSqlite; /** * The PDO Sqlite driver. * * @since 2.0 */ class Driver implements \Doctrine\DBAL\Driver { /** * @var array */ protected $_userDefinedFunctions = array( 'sqrt' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfSqrt'), 'numArgs' => 1), 'mod' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfMod'), 'numArgs' => 2), 'locate' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfLocate'), 'numArgs' => -1), ); /** * Tries to establish a database connection to SQLite. * * @param array $params * @param string $username * @param string $password * @param array $driverOptions * @return Connection */ public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { if (isset($driverOptions['userDefinedFunctions'])) { $this->_userDefinedFunctions = array_merge( $this->_userDefinedFunctions, $driverOptions['userDefinedFunctions']); unset($driverOptions['userDefinedFunctions']); } $pdo = new \Doctrine\DBAL\Driver\PDOConnection( $this->_constructPdoDsn($params), $username, $password, $driverOptions ); foreach ($this->_userDefinedFunctions AS $fn => $data) { $pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']); } return $pdo; } /** * Constructs the Sqlite PDO DSN. * * @return string The DSN. * @override */ protected function _constructPdoDsn(array $params) { $dsn = 'sqlite:'; if (isset($params['path'])) { $dsn .= $params['path']; } else if (isset($params['memory'])) { $dsn .= ':memory:'; } return $dsn; } /** * Gets the database platform that is relevant for this driver. */ public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\SqlitePlatform(); } /** * Gets the schema manager that is relevant for this driver. * * @param Doctrine\DBAL\Connection $conn * @return Doctrine\DBAL\Schema\SqliteSchemaManager */ public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\SqliteSchemaManager($conn); } public function getName() { return 'pdo_sqlite'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return isset($params['path']) ? $params['path'] : null; } }. */ namespace Doctrine\DBAL\Driver\OCI8; class OCI8Exception extends \Exception { static public function fromErrorInfo($error) { return new self($error['message'], $error['code']); } } . */ namespace Doctrine\DBAL\Driver\OCI8; use Doctrine\DBAL\Platforms; /** * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions. * * @author Roman Borschel * @since 2.0 */ class Driver implements \Doctrine\DBAL\Driver { public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) { return new OCI8Connection( $username, $password, $this->_constructDsn($params) ); } /** * Constructs the Oracle DSN. * * @return string The DSN. */ private function _constructDsn(array $params) { $dsn = ''; if (isset($params['host'])) { $dsn .= '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' . '(HOST=' . $params['host'] . ')'; if (isset($params['port'])) { $dsn .= '(PORT=' . $params['port'] . ')'; } else { $dsn .= '(PORT=1521)'; } $dsn .= '))'; if (isset($params['dbname'])) { $dsn .= '(CONNECT_DATA=(SID=' . $params['dbname'] . ')'; } $dsn .= '))'; } else { $dsn .= $params['dbname']; } if (isset($params['charset'])) { $dsn .= ';charset=' . $params['charset']; } return $dsn; } public function getDatabasePlatform() { return new \Doctrine\DBAL\Platforms\OraclePlatform(); } public function getSchemaManager(\Doctrine\DBAL\Connection $conn) { return new \Doctrine\DBAL\Schema\OracleSchemaManager($conn); } public function getName() { return 'oci8'; } public function getDatabase(\Doctrine\DBAL\Connection $conn) { $params = $conn->getParams(); return $params['user']; } }. */ namespace Doctrine\DBAL\Driver\OCI8; use \PDO; /** * The OCI8 implementation of the Statement interface. * * @since 2.0 * @author Roman Borschel */ class OCI8Statement implements \Doctrine\DBAL\Driver\Statement { /** Statement handle. */ private $_sth; private static $_PARAM = ':param'; private static $fetchStyleMap = array( PDO::FETCH_BOTH => OCI_BOTH, PDO::FETCH_ASSOC => OCI_ASSOC, PDO::FETCH_NUM => OCI_NUM ); private $_paramMap = array(); /** * Creates a new OCI8Statement that uses the given connection handle and SQL statement. * * @param resource $dbh The connection handle. * @param string $statement The SQL statement. */ public function __construct($dbh, $statement) { list($statement, $paramMap) = self::convertPositionalToNamedPlaceholders($statement); $this->_sth = oci_parse($dbh, $statement); $this->_paramMap = $paramMap; } /** * Convert positional (?) into named placeholders (:param) * * Oracle does not support positional parameters, hence this method converts all * positional parameters into artificially named parameters. Note that this conversion * is not perfect. All question marks (?) in the original statement are treated as * placeholders and converted to a named parameter. * * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral. * Question marks inside literal strings are therefore handled correctly by this method. * This comes at a cost, the whole sql statement has to be looped over. * * @todo extract into utility class in Doctrine\DBAL\Util namespace * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. * @param string $statement The SQL statement to convert. * @return string */ static public function convertPositionalToNamedPlaceholders($statement) { $count = 1; $inLiteral = false; // a valid query never starts with quotes $stmtLen = strlen($statement); $paramMap = array(); for ($i = 0; $i < $stmtLen; $i++) { if ($statement[$i] == '?' && !$inLiteral) { // real positional parameter detected $paramMap[$count] = ":param$count"; $len = strlen($paramMap[$count]); $statement = substr_replace($statement, ":param$count", $i, 1); $i += $len-1; // jump ahead $stmtLen = strlen($statement); // adjust statement length ++$count; } else if ($statement[$i] == "'" || $statement[$i] == '"') { $inLiteral = ! $inLiteral; // switch state! } } return array($statement, $paramMap); } /** * {@inheritdoc} */ public function bindValue($param, $value, $type = null) { return $this->bindParam($param, $value, $type); } /** * {@inheritdoc} */ public function bindParam($column, &$variable, $type = null) { $column = isset($this->_paramMap[$column]) ? $this->_paramMap[$column] : $column; return oci_bind_by_name($this->_sth, $column, $variable); } /** * Closes the cursor, enabling the statement to be executed again. * * @return boolean Returns TRUE on success or FALSE on failure. */ public function closeCursor() { return oci_free_statement($this->_sth); } /** * {@inheritdoc} */ public function columnCount() { return oci_num_fields($this->_sth); } /** * {@inheritdoc} */ public function errorCode() { $error = oci_error($this->_sth); if ($error !== false) { $error = $error['code']; } return $error; } /** * {@inheritdoc} */ public function errorInfo() { return oci_error($this->_sth); } /** * {@inheritdoc} */ public function execute($params = null) { if ($params) { $hasZeroIndex = isset($params[0]); foreach ($params as $key => $val) { if ($hasZeroIndex && is_numeric($key)) { $this->bindValue($key + 1, $val); } else { $this->bindValue($key, $val); } } } $ret = @oci_execute($this->_sth, OCI_DEFAULT); if ( ! $ret) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } return $ret; } /** * {@inheritdoc} */ public function fetch($fetchStyle = PDO::FETCH_BOTH) { if ( ! isset(self::$fetchStyleMap[$fetchStyle])) { throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle); } return oci_fetch_array($this->_sth, self::$fetchStyleMap[$fetchStyle] | OCI_RETURN_NULLS | OCI_RETURN_LOBS); } /** * {@inheritdoc} */ public function fetchAll($fetchStyle = PDO::FETCH_BOTH) { if ( ! isset(self::$fetchStyleMap[$fetchStyle])) { throw new \InvalidArgumentException("Invalid fetch style: " . $fetchStyle); } $result = array(); oci_fetch_all($this->_sth, $result, 0, -1, self::$fetchStyleMap[$fetchStyle] | OCI_RETURN_NULLS | OCI_FETCHSTATEMENT_BY_ROW | OCI_RETURN_LOBS); return $result; } /** * {@inheritdoc} */ public function fetchColumn($columnIndex = 0) { $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS); return $row[$columnIndex]; } /** * {@inheritdoc} */ public function rowCount() { return oci_num_rows($this->_sth); } }. */ namespace Doctrine\DBAL\Driver\OCI8; /** * OCI8 implementation of the Connection interface. * * @since 2.0 */ class OCI8Connection implements \Doctrine\DBAL\Driver\Connection { private $_dbh; /** * Create a Connection to an Oracle Database using oci8 extension. * * @param string $username * @param string $password * @param string $db */ public function __construct($username, $password, $db) { $this->_dbh = @oci_connect($username, $password, $db); if (!$this->_dbh) { throw new OCI8Exception($this->errorInfo()); } } /** * Create a non-executed prepared statement. * * @param string $prepareString * @return OCI8Statement */ public function prepare($prepareString) { return new OCI8Statement($this->_dbh, $prepareString); } /** * @param string $sql * @return OCI8Statement */ public function query() { $args = func_get_args(); $sql = $args[0]; //$fetchMode = $args[1]; $stmt = $this->prepare($sql); $stmt->execute(); return $stmt; } /** * Quote input value. * * @param mixed $input * @param int $type PDO::PARAM* * @return mixed */ public function quote($input, $type=\PDO::PARAM_STR) { return is_numeric($input) ? $input : "'$input'"; } /** * * @param string $statement * @return int */ public function exec($statement) { $stmt = $this->prepare($statement); $stmt->execute(); return $stmt->rowCount(); } public function lastInsertId($name = null) { //TODO: throw exception or support sequences? } /** * Start a transactiom * * Oracle has to explicitly set the autocommit mode off. That means * after connection, a commit or rollback there is always automatically * opened a new transaction. * * @return bool */ public function beginTransaction() { return true; } /** * @throws OCI8Exception * @return bool */ public function commit() { if (!oci_commit($this->_dbh)) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } return true; } /** * @throws OCI8Exception * @return bool */ public function rollBack() { if (!oci_rollback($this->_dbh)) { throw OCI8Exception::fromErrorInfo($this->errorInfo()); } return true; } public function errorCode() { $error = oci_error($this->_dbh); if ($error !== false) { $error = $error['code']; } return $error; } public function errorInfo() { return oci_error($this->_dbh); } }. */ namespace Doctrine\DBAL\Driver; /** * Connection interface. * Driver connections must implement this interface. * * This resembles (a subset of) the PDO interface. * * @since 2.0 */ interface Connection { function prepare($prepareString); function query(); function quote($input, $type=\PDO::PARAM_STR); function exec($statement); function lastInsertId($name = null); function beginTransaction(); function commit(); function rollBack(); function errorCode(); function errorInfo(); }. */ namespace Doctrine\DBAL; /** * Container for all DBAL events. * * This class cannot be instantiated. * * @author Roman Borschel * @since 2.0 */ final class Events { private function __construct() {} const postConnect = 'postConnect'; } . */ namespace Doctrine\DBAL; use Doctrine\Common\EventManager; /** * Factory for creating Doctrine\DBAL\Connection instances. * * @author Roman Borschel * @since 2.0 */ final class DriverManager { /** * List of supported drivers and their mappings to the driver classes. * * @var array * @todo REMOVE. Users should directly supply class names instead. */ private static $_driverMap = array( 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', 'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver', 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', ); /** Private constructor. This class cannot be instantiated. */ private function __construct() { } /** * Creates a connection object based on the specified parameters. * This method returns a Doctrine\DBAL\Connection which wraps the underlying * driver connection. * * $params must contain at least one of the following. * * Either 'driver' with one of the following values: * pdo_mysql * pdo_sqlite * pdo_pgsql * pdo_oracle * pdo_mssql * * OR 'driverClass' that contains the full class name (with namespace) of the * driver class to instantiate. * * Other (optional) parameters: * * user (string): * The username to use when connecting. * * password (string): * The password to use when connecting. * * driverOptions (array): * Any additional driver-specific options for the driver. These are just passed * through to the driver. * * pdo: * You can pass an existing PDO instance through this parameter. The PDO * instance will be wrapped in a Doctrine\DBAL\Connection. * * wrapperClass: * You may specify a custom wrapper class through the 'wrapperClass' * parameter but this class MUST inherit from Doctrine\DBAL\Connection. * * @param array $params The parameters. * @param Doctrine\DBAL\Configuration The configuration to use. * @param Doctrine\Common\EventManager The event manager to use. * @return Doctrine\DBAL\Connection */ public static function getConnection( array $params, Configuration $config = null, EventManager $eventManager = null) { // create default config and event manager, if not set if ( ! $config) { $config = new Configuration(); } if ( ! $eventManager) { $eventManager = new EventManager(); } // check for existing pdo object if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) { throw DBALException::invalidPdoInstance(); } else if (isset($params['pdo'])) { $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME); } else { self::_checkParams($params); } if (isset($params['driverClass'])) { $className = $params['driverClass']; } else { $className = self::$_driverMap[$params['driver']]; } $driver = new $className(); $wrapperClass = 'Doctrine\DBAL\Connection'; if (isset($params['wrapperClass'])) { if (is_subclass_of($params['wrapperClass'], $wrapperClass)) { $wrapperClass = $params['wrapperClass']; } else { throw DBALException::invalidWrapperClass($params['wrapperClass']); } } return new $wrapperClass($params, $driver, $config, $eventManager); } /** * Checks the list of parameters. * * @param array $params */ private static function _checkParams(array $params) { // check existance of mandatory parameters // driver if ( ! isset($params['driver']) && ! isset($params['driverClass'])) { throw DBALException::driverRequired(); } // check validity of parameters // driver if ( isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) { throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap)); } if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) { throw DBALException::invalidDriverClass($params['driverClass']); } } }. */ namespace Doctrine\DBAL; use Doctrine\DBAL\Logging\SQLLogger; /** * Configuration container for the Doctrine DBAL. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @internal When adding a new configuration option just write a getter/setter * pair and add the option to the _attributes array with a proper default value. */ class Configuration { /** * The attributes that are contained in the configuration. * Values are default values. * * @var array */ protected $_attributes = array(); /** * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. * * @param SQLLogger $logger */ public function setSQLLogger(SQLLogger $logger) { $this->_attributes['sqlLogger'] = $logger; } /** * Gets the SQL logger that is used. * * @return SQLLogger */ public function getSQLLogger() { return isset($this->_attributes['sqlLogger']) ? $this->_attributes['sqlLogger'] : null; } }. */ namespace Doctrine\DBAL; use PDO, Doctrine\DBAL\Types\Type, Doctrine\DBAL\Driver\Statement as DriverStatement; /** * A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support * for logging, DBAL mapping types, etc. * * @author Roman Borschel * @since 2.0 */ class Statement implements DriverStatement { /** * @var string The SQL statement. */ private $_sql; /** * @var array The bound parameters. */ private $_params = array(); /** * @var Doctrine\DBAL\Driver\Statement The underlying driver statement. */ private $_stmt; /** * @var Doctrine\DBAL\Platforms\AbstractPlatform The underlying database platform. */ private $_platform; /** * @var Doctrine\DBAL\Connection The connection this statement is bound to and executed on. */ private $_conn; /** * Creates a new Statement for the given SQL and Connection. * * @param string $sql The SQL of the statement. * @param Doctrine\DBAL\Connection The connection on which the statement should be executed. */ public function __construct($sql, Connection $conn) { $this->_sql = $sql; $this->_stmt = $conn->getWrappedConnection()->prepare($sql); $this->_conn = $conn; $this->_platform = $conn->getDatabasePlatform(); } /** * Binds a parameter value to the statement. * * The value can optionally be bound with a PDO binding type or a DBAL mapping type. * If bound with a DBAL mapping type, the binding type is derived from the mapping * type and the value undergoes the conversion routines of the mapping type before * being bound. * * @param $name The name or position of the parameter. * @param $value The value of the parameter. * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. * @return boolean TRUE on success, FALSE on failure. */ public function bindValue($name, $value, $type = null) { $this->_params[$name] = $value; if ($type !== null) { if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->_platform); $bindingType = $type->getBindingType(); } else { $bindingType = $type; // PDO::PARAM_* constants } return $this->_stmt->bindValue($name, $value, $bindingType); } else { return $this->_stmt->bindValue($name, $value); } } /** * Binds a parameter to a value by reference. * * Binding a parameter by reference does not support DBAL mapping types. * * @param string $name The name or position of the parameter. * @param mixed $value The reference to the variable to bind * @param integer $type The PDO binding type. * @return boolean TRUE on success, FALSE on failure. */ public function bindParam($name, &$var, $type = PDO::PARAM_STR) { return $this->_stmt->bindParam($name, $var, $type); } /** * Executes the statement with the currently bound parameters. * * @return boolean TRUE on success, FALSE on failure. */ public function execute($params = null) { $hasLogger = $this->_conn->getConfiguration()->getSQLLogger(); if ($hasLogger) { $this->_conn->getConfiguration()->getSQLLogger()->startQuery($this->_sql, $this->_params); } $stmt = $this->_stmt->execute($params); if ($hasLogger) { $this->_conn->getConfiguration()->getSQLLogger()->stopQuery(); } $this->_params = array(); return $stmt; } /** * Closes the cursor, freeing the database resources used by this statement. * * @return boolean TRUE on success, FALSE on failure. */ public function closeCursor() { return $this->_stmt->closeCursor(); } /** * Returns the number of columns in the result set. * * @return integer */ public function columnCount() { return $this->_stmt->columnCount(); } /** * Fetches the SQLSTATE associated with the last operation on the statement. * * @return string */ public function errorCode() { return $this->_stmt->errorCode(); } /** * Fetches extended error information associated with the last operation on the statement. * * @return array */ public function errorInfo() { return $this->_stmt->errorInfo(); } /** * Fetches the next row from a result set. * * @param integer $fetchStyle * @return mixed The return value of this function on success depends on the fetch type. * In all cases, FALSE is returned on failure. */ public function fetch($fetchStyle = PDO::FETCH_BOTH) { return $this->_stmt->fetch($fetchStyle); } /** * Returns an array containing all of the result set rows. * * @param integer $fetchStyle * @param integer $columnIndex * @return array An array containing all of the remaining rows in the result set. */ public function fetchAll($fetchStyle = PDO::FETCH_BOTH, $columnIndex = 0) { if ($columnIndex != 0) { return $this->_stmt->fetchAll($fetchStyle, $columnIndex); } return $this->_stmt->fetchAll($fetchStyle); } /** * Returns a single column from the next row of a result set. * * @param integer $columnIndex * @return mixed A single column from the next row of a result set or FALSE if there are no more rows. */ public function fetchColumn($columnIndex = 0) { return $this->_stmt->fetchColumn($columnIndex); } /** * Returns the number of rows affected by the last execution of this statement. * * @return integer The number of affected rows. */ public function rowCount() { return $this->_stmt->rowCount(); } /** * Gets the wrapped driver statement. * * @return Doctrine\DBAL\Driver\Statement */ public function getWrappedStatement() { return $this->_stmt; } }. */ namespace Doctrine\DBAL\Schema; /** * Schema manager for the MySql RDBMS. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Roman Borschel * @author Benjamin Eberlei * @version $Revision$ * @since 2.0 */ class MySqlSchemaManager extends AbstractSchemaManager { protected function _getPortableViewDefinition($view) { return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); } protected function _getPortableTableDefinition($table) { return array_shift($table); } protected function _getPortableUserDefinition($user) { return array( 'user' => $user['User'], 'password' => $user['Password'], ); } protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) { foreach($tableIndexes AS $k => $v) { $v = array_change_key_case($v, CASE_LOWER); if($v['key_name'] == 'PRIMARY') { $v['primary'] = true; } else { $v['primary'] = false; } $tableIndexes[$k] = $v; } return parent::_getPortableTableIndexesList($tableIndexes, $tableName); } protected function _getPortableSequenceDefinition($sequence) { return end($sequence); } protected function _getPortableDatabaseDefinition($database) { return $database['Database']; } /** * Gets a portable column definition. * * The database type is mapped to a corresponding Doctrine mapping type. * * @param $tableColumn * @return array */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); $dbType = strtolower($tableColumn['type']); $dbType = strtok($dbType, '(), '); if (isset($tableColumn['length'])) { $length = $tableColumn['length']; $decimal = ''; } else { $length = strtok('(), '); $decimal = strtok('(), ') ? strtok('(), '):null; } $type = array(); $unsigned = $fixed = null; if ( ! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $scale = null; $precision = null; $type = $this->_platform->getDoctrineTypeMapping($dbType); switch ($dbType) { case 'char': $fixed = true; break; case 'float': case 'double': case 'real': case 'numeric': case 'decimal': if(preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) { $precision = $match[1]; $scale = $match[2]; $length = null; } break; case 'tinyint': case 'smallint': case 'mediumint': case 'int': case 'integer': case 'bigint': case 'tinyblob': case 'mediumblob': case 'longblob': case 'blob': case 'binary': case 'varbinary': case 'year': $length = null; break; } $length = ((int) $length == 0) ? null : (int) $length; $def = array( 'type' => $type, 'length' => $length, 'unsigned' => (bool) $unsigned, 'fixed' => (bool) $fixed ); $options = array( 'length' => $length, 'unsigned' => (bool)$unsigned, 'fixed' => (bool)$fixed, 'default' => $tableColumn['default'], 'notnull' => (bool) ($tableColumn['null'] != 'YES'), 'scale' => null, 'precision' => null, 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false), ); if ($scale !== null && $precision !== null) { $options['scale'] = $scale; $options['precision'] = $precision; } return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); } public function _getPortableTableForeignKeyDefinition($tableForeignKey) { $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); if (!isset($tableForeignKey['delete_rule']) || $tableForeignKey['delete_rule'] == "RESTRICT") { $tableForeignKey['delete_rule'] = null; } if (!isset($tableForeignKey['update_rule']) || $tableForeignKey['update_rule'] == "RESTRICT") { $tableForeignKey['update_rule'] = null; } return new ForeignKeyConstraint( (array)$tableForeignKey['column_name'], $tableForeignKey['referenced_table_name'], (array)$tableForeignKey['referenced_column_name'], $tableForeignKey['constraint_name'], array( 'onUpdate' => $tableForeignKey['update_rule'], 'onDelete' => $tableForeignKey['delete_rule'], ) ); } }. */ namespace Doctrine\DBAL\Schema; /** * xxx * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Benjamin Eberlei * @version $Revision$ * @since 2.0 */ class PostgreSqlSchemaManager extends AbstractSchemaManager { protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { $onUpdate = null; $onDelete = null; if (preg_match('(ON UPDATE ([a-zA-Z0-9]+))', $tableForeignKey['condef'], $match)) { $onUpdate = $match[1]; } if (preg_match('(ON DELETE ([a-zA-Z0-9]+))', $tableForeignKey['condef'], $match)) { $onDelete = $match[1]; } if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) { $localColumns = array_map('trim', explode(",", $values[1])); $foreignColumns = array_map('trim', explode(",", $values[3])); $foreignTable = $values[2]; } return new ForeignKeyConstraint( $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'], array('onUpdate' => $onUpdate, 'onDelete' => $onDelete) ); } public function dropDatabase($database) { $params = $this->_conn->getParams(); $params["dbname"] = "postgres"; $tmpPlatform = $this->_platform; $tmpConn = $this->_conn; $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); $this->_platform = $this->_conn->getDatabasePlatform(); parent::dropDatabase($database); $this->_platform = $tmpPlatform; $this->_conn = $tmpConn; } public function createDatabase($database) { $params = $this->_conn->getParams(); $params["dbname"] = "postgres"; $tmpPlatform = $this->_platform; $tmpConn = $this->_conn; $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); $this->_platform = $this->_conn->getDatabasePlatform(); parent::createDatabase($database); $this->_platform = $tmpPlatform; $this->_conn = $tmpConn; } protected function _getPortableTriggerDefinition($trigger) { return $trigger['trigger_name']; } protected function _getPortableViewDefinition($view) { return new View($view['viewname'], $view['definition']); } protected function _getPortableUserDefinition($user) { return array( 'user' => $user['usename'], 'password' => $user['passwd'] ); } protected function _getPortableTableDefinition($table) { return $table['table_name']; } /** * @license New BSD License * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html * @param array $tableIndexes * @param string $tableName * @return array */ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) { $buffer = array(); foreach ($tableIndexes AS $row) { $colNumbers = explode(' ', $row['indkey']); $colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )'; $columnNameSql = "SELECT attnum, attname FROM pg_attribute WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;"; $stmt = $this->_conn->executeQuery($columnNameSql); $indexColumns = $stmt->fetchAll(); // required for getting the order of the columns right. foreach ($colNumbers AS $colNum) { foreach ($indexColumns as $colRow) { if ($colNum == $colRow['attnum']) { $buffer[] = array( 'key_name' => $row['relname'], 'column_name' => trim($colRow['attname']), 'non_unique' => !$row['indisunique'], 'primary' => $row['indisprimary'] ); } } } } return parent::_getPortableTableIndexesList($buffer); } protected function _getPortableDatabaseDefinition($database) { return $database['datname']; } protected function _getPortableSequenceDefinition($sequence) { $data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $sequence['relname']); return new Sequence($sequence['relname'], $data[0]['increment_by'], $data[0]['min_value']); } protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); if (strtolower($tableColumn['type']) === 'varchar') { // get length from varchar definition $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); $tableColumn['length'] = $length; } $matches = array(); $autoincrement = false; if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) { $tableColumn['sequence'] = $matches[1]; $tableColumn['default'] = null; $autoincrement = true; } if (stripos($tableColumn['default'], 'NULL') === 0) { $tableColumn['default'] = null; } $length = (isset($tableColumn['length'])) ? $tableColumn['length'] : null; if ($length == '-1' && isset($tableColumn['atttypmod'])) { $length = $tableColumn['atttypmod'] - 4; } if ((int) $length <= 0) { $length = null; } $type = array(); $fixed = null; if (!isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $precision = null; $scale = null; if ($this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) { $dbType = strtolower($tableColumn['type']); } else { $dbType = strtolower($tableColumn['domain_type']); $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; } $type = $this->_platform->getDoctrineTypeMapping($dbType); switch ($dbType) { case 'smallint': case 'int2': $length = null; break; case 'int': case 'int4': case 'integer': $length = null; break; case 'bigint': case 'int8': $length = null; break; case 'bool': case 'boolean': $length = null; break; case 'text': $fixed = false; break; case 'varchar': case 'interval': case '_varchar': $fixed = false; break; case 'char': case 'bpchar': $fixed = true; break; case 'float': case 'float4': case 'float8': case 'double': case 'double precision': case 'real': case 'decimal': case 'money': case 'numeric': if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) { $precision = $match[1]; $scale = $match[2]; $length = null; } break; case 'year': $length = null; break; } $options = array( 'length' => $length, 'notnull' => (bool) $tableColumn['isnotnull'], 'default' => $tableColumn['default'], 'primary' => (bool) ($tableColumn['pri'] == 't'), 'precision' => $precision, 'scale' => $scale, 'fixed' => $fixed, 'unsigned' => false, 'autoincrement' => $autoincrement, ); return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); } }. */ namespace Doctrine\DBAL\Schema; /** * Representation of a Database View * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei */ class View extends AbstractAsset { /** * @var string */ private $_sql; public function __construct($name, $sql) { $this->_setName($name); $this->_sql = $sql; } /** * @return string */ public function getSql() { return $this->_sql; } }. */ namespace Doctrine\DBAL\Schema; /** * IBM Db2 Schema Manager * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei */ class DB2SchemaManager extends AbstractSchemaManager { /** * Return a list of all tables in the current database * * Apparently creator is the schema not the user who created it: * {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm} * * @return array */ public function listTableNames() { $sql = $this->_platform->getListTablesSQL(); $sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')"; $tables = $this->_conn->fetchAll($sql); return $this->_getPortableTablesList($tables); } /** * Get Table Column Definition * * @param array $tableColumn * @return Column */ protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = array_change_key_case($tableColumn, \CASE_LOWER); $length = null; $fixed = null; $unsigned = false; $scale = false; $precision = false; $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); switch (strtolower($tableColumn['typename'])) { case 'varchar': $length = $tableColumn['length']; $fixed = false; break; case 'character': $length = $tableColumn['length']; $fixed = true; break; case 'clob': $length = $tableColumn['length']; break; case 'decimal': case 'double': case 'real': $scale = $tableColumn['scale']; $precision = $tableColumn['length']; break; } $options = array( 'length' => $length, 'unsigned' => (bool)$unsigned, 'fixed' => (bool)$fixed, 'default' => ($tableColumn['default'] == "NULL") ? null : $tableColumn['default'], 'notnull' => (bool) ($tableColumn['nulls'] == 'N'), 'scale' => null, 'precision' => null, 'platformOptions' => array(), ); if ($scale !== null && $precision !== null) { $options['scale'] = $scale; $options['precision'] = $precision; } return new Column($tableColumn['colname'], \Doctrine\DBAL\Types\Type::getType($type), $options); } protected function _getPortableTablesList($tables) { $tableNames = array(); foreach ($tables AS $tableRow) { $tableRow = array_change_key_case($tableRow, \CASE_LOWER); $tableNames[] = $tableRow['name']; } return $tableNames; } protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) { $tableIndexRows = array(); $indexes = array(); foreach($tableIndexes AS $indexKey => $data) { $data = array_change_key_case($data, \CASE_LOWER); $unique = ($data['uniquerule'] == "D") ? false : true; $primary = ($data['uniquerule'] == "P"); $indexName = strtolower($data['name']); if ($primary) { $keyName = 'primary'; } else { $keyName = $indexName; } $indexes[$keyName] = new Index($indexName, explode("+", ltrim($data['colnames'], '+')), $unique, $primary); } return $indexes; } protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); $tableForeignKey['deleterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['deleterule']); $tableForeignKey['updaterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['updaterule']); return new ForeignKeyConstraint( array_map('trim', (array)$tableForeignKey['fkcolnames']), $tableForeignKey['reftbname'], array_map('trim', (array)$tableForeignKey['pkcolnames']), $tableForeignKey['relname'], array( 'onUpdate' => $tableForeignKey['updaterule'], 'onDelete' => $tableForeignKey['deleterule'], ) ); } protected function _getPortableForeignKeyRuleDef($def) { if ($def == "C") { return "CASCADE"; } else if ($def == "N") { return "SET NULL"; } return null; } protected function _getPortableViewDefinition($view) { $view = array_change_key_case($view, \CASE_LOWER); // sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199 //$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']); if (!is_resource($view['text'])) { $pos = strpos($view['text'], ' AS '); $sql = substr($view['text'], $pos+4); } else { $sql = ''; } return new View($view['name'], $sql); } }. */ namespace Doctrine\DBAL\Schema; /** * SqliteSchemaManager * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Jonathan H. Wage * @version $Revision$ * @since 2.0 */ class SqliteSchemaManager extends AbstractSchemaManager { /** * {@inheritdoc} * * @override */ public function dropDatabase($database) { if (file_exists($database)) { unlink($database); } } /** * {@inheritdoc} * * @override */ public function createDatabase($database) { $params = $this->_conn->getParams(); $driver = $params['driver']; $options = array( 'driver' => $driver, 'path' => $database ); $conn = \Doctrine\DBAL\DriverManager::getConnection($options); $conn->connect(); $conn->close(); } protected function _getPortableTableDefinition($table) { return $table['name']; } /** * @license New BSD License * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html * @param array $tableIndexes * @param string $tableName * @return array */ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) { $indexBuffer = array(); // fetch primary $stmt = $this->_conn->executeQuery( "PRAGMA TABLE_INFO ('$tableName')" ); $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach($indexArray AS $indexColumnRow) { if($indexColumnRow['pk'] == "1") { $indexBuffer[] = array( 'key_name' => 'primary', 'primary' => true, 'non_unique' => false, 'column_name' => $indexColumnRow['name'] ); } } // fetch regular indexes foreach($tableIndexes AS $tableIndex) { $keyName = $tableIndex['name']; $idx = array(); $idx['key_name'] = $keyName; $idx['primary'] = false; $idx['non_unique'] = $tableIndex['unique']?false:true; $stmt = $this->_conn->executeQuery( "PRAGMA INDEX_INFO ( '{$keyName}' )" ); $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); foreach ( $indexArray as $indexColumnRow ) { $idx['column_name'] = $indexColumnRow['name']; $indexBuffer[] = $idx; } } return parent::_getPortableTableIndexesList($indexBuffer, $tableName); } protected function _getPortableTableIndexDefinition($tableIndex) { return array( 'name' => $tableIndex['name'], 'unique' => (bool) $tableIndex['unique'] ); } protected function _getPortableTableColumnDefinition($tableColumn) { $e = explode('(', $tableColumn['type']); $tableColumn['type'] = $e[0]; if (isset($e[1])) { $length = trim($e[1], ')'); $tableColumn['length'] = $length; } $dbType = strtolower($tableColumn['type']); $length = isset($tableColumn['length']) ? $tableColumn['length'] : null; $unsigned = (boolean) isset($tableColumn['unsigned']) ? $tableColumn['unsigned'] : false; $fixed = false; $type = $this->_platform->getDoctrineTypeMapping($dbType); $default = $tableColumn['dflt_value']; if ($default == 'NULL') { $default = null; } $notnull = (bool) $tableColumn['notnull']; if ( ! isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $precision = null; $scale = null; switch ($dbType) { case 'char': $fixed = true; break; case 'float': case 'double': case 'real': case 'decimal': case 'numeric': list($precision, $scale) = array_map('trim', explode(', ', $tableColumn['length'])); $length = null; break; } $options = array( 'length' => $length, 'unsigned' => (bool) $unsigned, 'fixed' => $fixed, 'notnull' => $notnull, 'default' => $default, 'precision' => $precision, 'scale' => $scale, 'autoincrement' => (bool) $tableColumn['pk'], ); return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options); } protected function _getPortableViewDefinition($view) { return new View($view['name'], $view['sql']); } }. */ namespace Doctrine\DBAL\Schema; /** * Compare to Schemas and return an instance of SchemaDiff * * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. * @license http://ez.no/licenses/new_bsd New BSD License * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class Comparator { /** * @param Schema $fromSchema * @param Schema $toSchema * @return SchemaDiff */ static public function compareSchemas( Schema $fromSchema, Schema $toSchema ) { $c = new self(); return $c->compare($fromSchema, $toSchema); } /** * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. * * The returned diferences are returned in such a way that they contain the * operations to change the schema stored in $fromSchema to the schema that is * stored in $toSchema. * * @param Schema $fromSchema * @param Schema $toSchema * * @return SchemaDiff */ public function compare(Schema $fromSchema, Schema $toSchema) { $diff = new SchemaDiff(); $foreignKeysToTable = array(); foreach ( $toSchema->getTables() AS $tableName => $table ) { if ( !$fromSchema->hasTable($tableName) ) { $diff->newTables[$tableName] = $table; } else { $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $table ); if ( $tableDifferences !== false ) { $diff->changedTables[$tableName] = $tableDifferences; } } } /* Check if there are tables removed */ foreach ( $fromSchema->getTables() AS $tableName => $table ) { if ( !$toSchema->hasTable($tableName) ) { $diff->removedTables[$tableName] = $table; } // also remember all foreign keys that point to a specific table foreach ($table->getForeignKeys() AS $foreignKey) { $foreignTable = strtolower($foreignKey->getForeignTableName()); if (!isset($foreignKeysToTable[$foreignTable])) { $foreignKeysToTable[$foreignTable] = array(); } $foreignKeysToTable[$foreignTable][] = $foreignKey; } } foreach ($diff->removedTables AS $tableName => $table) { if (isset($foreignKeysToTable[$tableName])) { $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]); } } foreach ( $toSchema->getSequences() AS $sequenceName => $sequence) { if (!$fromSchema->hasSequence($sequenceName)) { $diff->newSequences[] = $sequence; } else { if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { $diff->changedSequences[] = $fromSchema->getSequence($sequenceName); } } } foreach ($fromSchema->getSequences() AS $sequenceName => $sequence) { if (!$toSchema->hasSequence($sequenceName)) { $diff->removedSequences[] = $sequence; } } return $diff; } /** * * @param Sequence $sequence1 * @param Sequence $sequence2 */ public function diffSequence(Sequence $sequence1, Sequence $sequence2) { if($sequence1->getAllocationSize() != $sequence2->getAllocationSize()) { return true; } if($sequence1->getInitialValue() != $sequence2->getInitialValue()) { return true; } return false; } /** * Returns the difference between the tables $table1 and $table2. * * If there are no differences this method returns the boolean false. * * @param Table $table1 * @param Table $table2 * * @return bool|TableDiff */ public function diffTable(Table $table1, Table $table2) { $changes = 0; $tableDifferences = new TableDiff($table1->getName()); $table1Columns = $table1->getColumns(); $table2Columns = $table2->getColumns(); /* See if all the fields in table 1 exist in table 2 */ foreach ( $table2Columns as $columnName => $column ) { if ( !$table1->hasColumn($columnName) ) { $tableDifferences->addedColumns[$columnName] = $column; $changes++; } } /* See if there are any removed fields in table 2 */ foreach ( $table1Columns as $columnName => $column ) { if ( !$table2->hasColumn($columnName) ) { $tableDifferences->removedColumns[$columnName] = $column; $changes++; } } foreach ( $table1Columns as $columnName => $column ) { if ( $table2->hasColumn($columnName) ) { $changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) ); if (count($changedProperties) ) { $columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties); $tableDifferences->changedColumns[$column->getName()] = $columnDiff; $changes++; } } } $this->detectColumnRenamings($tableDifferences); $table1Indexes = $table1->getIndexes(); $table2Indexes = $table2->getIndexes(); foreach ($table2Indexes AS $index2Name => $index2Definition) { foreach ($table1Indexes AS $index1Name => $index1Definition) { if ($this->diffIndex($index1Definition, $index2Definition) === false) { unset($table1Indexes[$index1Name]); unset($table2Indexes[$index2Name]); } else { if ($index1Name == $index2Name) { $tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name]; unset($table1Indexes[$index1Name]); unset($table2Indexes[$index2Name]); $changes++; } } } } foreach ($table1Indexes AS $index1Name => $index1Definition) { $tableDifferences->removedIndexes[$index1Name] = $index1Definition; $changes++; } foreach ($table2Indexes AS $index2Name => $index2Definition) { $tableDifferences->addedIndexes[$index2Name] = $index2Definition; $changes++; } $fromFkeys = $table1->getForeignKeys(); $toFkeys = $table2->getForeignKeys(); foreach ($fromFkeys AS $key1 => $constraint1) { foreach ($toFkeys AS $key2 => $constraint2) { if($this->diffForeignKey($constraint1, $constraint2) === false) { unset($fromFkeys[$key1]); unset($toFkeys[$key2]); } else { if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) { $tableDifferences->changedForeignKeys[] = $constraint2; $changes++; unset($fromFkeys[$key1]); unset($toFkeys[$key2]); } } } } foreach ($fromFkeys AS $key1 => $constraint1) { $tableDifferences->removedForeignKeys[] = $constraint1; $changes++; } foreach ($toFkeys AS $key2 => $constraint2) { $tableDifferences->addedForeignKeys[] = $constraint2; $changes++; } return $changes ? $tableDifferences : false; } /** * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop * however ambiguouties between different possibilites should not lead to renaming at all. * * @param TableDiff $tableDifferences */ private function detectColumnRenamings(TableDiff $tableDifferences) { $renameCandidates = array(); foreach ($tableDifferences->addedColumns AS $addedColumnName => $addedColumn) { foreach ($tableDifferences->removedColumns AS $removedColumnName => $removedColumn) { if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) { $renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn); } } } foreach ($renameCandidates AS $candidate => $candidateColumns) { if (count($candidateColumns) == 1) { list($removedColumn, $addedColumn) = $candidateColumns[0]; $tableDifferences->renamedColumns[$removedColumn->getName()] = $addedColumn; unset($tableDifferences->addedColumns[$addedColumnName]); unset($tableDifferences->removedColumns[$removedColumnName]); } } } /** * @param ForeignKeyConstraint $key1 * @param ForeignKeyConstraint $key2 * @return bool */ public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) { if (array_map('strtolower', $key1->getLocalColumns()) != array_map('strtolower', $key2->getLocalColumns())) { return true; } if (array_map('strtolower', $key1->getForeignColumns()) != array_map('strtolower', $key2->getForeignColumns())) { return true; } if ($key1->onUpdate() != $key2->onUpdate()) { return true; } if ($key1->onDelete() != $key2->onDelete()) { return true; } return false; } /** * Returns the difference between the fields $field1 and $field2. * * If there are differences this method returns $field2, otherwise the * boolean false. * * @param Column $column1 * @param Column $column2 * * @return array */ public function diffColumn(Column $column1, Column $column2) { $changedProperties = array(); if ( $column1->getType() != $column2->getType() ) { $changedProperties[] = 'type'; } if ($column1->getNotnull() != $column2->getNotnull()) { $changedProperties[] = 'notnull'; } if ($column1->getDefault() != $column2->getDefault()) { $changedProperties[] = 'default'; } if ($column1->getUnsigned() != $column2->getUnsigned()) { $changedProperties[] = 'unsigned'; } if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) { if ($column1->getLength() != $column2->getLength()) { $changedProperties[] = 'length'; } if ($column1->getFixed() != $column2->getFixed()) { $changedProperties[] = 'fixed'; } } if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { if ($column1->getPrecision() != $column2->getPrecision()) { $changedProperties[] = 'precision'; } if ($column1->getScale() != $column2->getScale()) { $changedProperties[] = 'scale'; } } if ($column1->getAutoincrement() != $column2->getAutoincrement()) { $changedProperties[] = 'autoincrement'; } return $changedProperties; } /** * Finds the difference between the indexes $index1 and $index2. * * Compares $index1 with $index2 and returns $index2 if there are any * differences or false in case there are no differences. * * @param Index $index1 * @param Index $index2 * @return bool */ public function diffIndex(Index $index1, Index $index2) { if ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)) { return false; } return true; } } . */ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\Visitor; class Index extends AbstractAsset implements Constraint { /** * @var array */ protected $_columns; /** * @var bool */ protected $_isUnique = false; /** * @var bool */ protected $_isPrimary = false; /** * @param string $indexName * @param array $column * @param bool $isUnique * @param bool $isPrimary */ public function __construct($indexName, array $columns, $isUnique=false, $isPrimary=false) { $isUnique = ($isPrimary)?true:$isUnique; $this->_setName($indexName); $this->_isUnique = $isUnique; $this->_isPrimary = $isPrimary; foreach($columns AS $column) { $this->_addColumn($column); } } /** * @param string $column */ protected function _addColumn($column) { if(is_string($column)) { $this->_columns[] = strtolower($column); } else { throw new \InvalidArgumentException("Expecting a string as Index Column"); } } /** * @return array */ public function getColumns() { return $this->_columns; } /** * @return bool */ public function isUnique() { return $this->_isUnique; } /** * @return bool */ public function isPrimary() { return $this->_isPrimary; } /** * @param string $columnName * @param int $pos * @return bool */ public function hasColumnAtPosition($columnName, $pos=0) { $columnName = strtolower($columnName); $indexColumns = \array_map('strtolower', $this->getColumns()); return \array_search($columnName, $indexColumns) === $pos; } /** * Check if this index exactly spans the given column names in the correct order. * * @param array $columnNames * @return boolean */ public function spansColumns(array $columnNames) { $sameColumns = true; for ($i = 0; $i < count($this->_columns); $i++) { if (!isset($columnNames[$i]) || strtolower($this->_columns[$i]) != strtolower($columnNames[$i])) { $sameColumns = false; } } return $sameColumns; } /** * Check if the other index already fullfills all the indexing and constraint needs of the current one. * * @param Index $other * @return bool */ public function isFullfilledBy(Index $other) { // allow the other index to be equally large only. It being larger is an option // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) if (count($other->getColumns()) != count($this->getColumns())) { return false; } // Check if columns are the same, and even in the same order $sameColumns = $this->spansColumns($other->getColumns()); if ($sameColumns) { if (!$this->isUnique() && !$this->isPrimary()) { // this is a special case: If the current key is neither primary or unique, any uniqe or // primary key will always have the same effect for the index and there cannot be any constraint // overlaps. This means a primary or unique index can always fullfill the requirements of just an // index that has no constraints. return true; } else if ($other->isPrimary() != $this->isPrimary()) { return false; } else if ($other->isUnique() != $this->isUnique()) { return false; } return true; } return false; } /** * Detect if the other index is a non-unique, non primary index that can be overwritten by this one. * * @param Index $other * @return bool */ public function overrules(Index $other) { if ($other->isPrimary() || $other->isUnique()) { return false; } if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique())) { return true; } return false; } }. */ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Types; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Base class for schema managers. Schema managers are used to inspect and/or * modify the database schema/structure. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Roman Borschel * @author Jonathan H. Wage * @author Benjamin Eberlei * @version $Revision$ * @since 2.0 */ abstract class AbstractSchemaManager { /** * Holds instance of the Doctrine connection for this schema manager * * @var \Doctrine\DBAL\Connection */ protected $_conn; /** * Holds instance of the database platform used for this schema manager * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; /** * Constructor. Accepts the Connection instance to manage the schema for * * @param \Doctrine\DBAL\Connection $conn */ public function __construct(\Doctrine\DBAL\Connection $conn) { $this->_conn = $conn; $this->_platform = $this->_conn->getDatabasePlatform(); } /** * Return associated platform. * * @return \Doctrine\DBAL\Platform\AbstractPlatform */ public function getDatabasePlatform() { return $this->_platform; } /** * Try any method on the schema manager. Normally a method throws an * exception when your DBMS doesn't support it or if an error occurs. * This method allows you to try and method on your SchemaManager * instance and will return false if it does not work or is not supported. * * * $result = $sm->tryMethod('dropView', 'view_name'); * * * @return mixed */ public function tryMethod() { $args = func_get_args(); $method = $args[0]; unset($args[0]); $args = array_values($args); try { return call_user_func_array(array($this, $method), $args); } catch (\Exception $e) { return false; } } /** * List the available databases for this connection * * @return array $databases */ public function listDatabases() { $sql = $this->_platform->getListDatabasesSQL(); $databases = $this->_conn->fetchAll($sql); return $this->_getPortableDatabasesList($databases); } /** * List the available sequences for this connection * * @return Sequence[] */ public function listSequences($database = null) { if (is_null($database)) { $database = $this->_conn->getDatabase(); } $sql = $this->_platform->getListSequencesSQL($database); $sequences = $this->_conn->fetchAll($sql); return $this->_getPortableSequencesList($sequences); } /** * List the columns for a given table. * * In contrast to other libraries and to the old version of Doctrine, * this column definition does try to contain the 'primary' field for * the reason that it is not portable accross different RDBMS. Use * {@see listTableIndexes($tableName)} to retrieve the primary key * of a table. We're a RDBMS specifies more details these are held * in the platformDetails array. * * @param string $table The name of the table. * @return Column[] */ public function listTableColumns($table) { $sql = $this->_platform->getListTableColumnsSQL($table); $tableColumns = $this->_conn->fetchAll($sql); return $this->_getPortableTableColumnList($tableColumns); } /** * List the indexes for a given table returning an array of Index instances. * * Keys of the portable indexes list are all lower-cased. * * @param string $table The name of the table * @return Index[] $tableIndexes */ public function listTableIndexes($table) { $sql = $this->_platform->getListTableIndexesSQL($table); $tableIndexes = $this->_conn->fetchAll($sql); return $this->_getPortableTableIndexesList($tableIndexes, $table); } /** * Return true if all the given tables exist. * * @param array $tableNames * @return bool */ public function tablesExist($tableNames) { $tableNames = array_map('strtolower', (array)$tableNames); return count($tableNames) == count(\array_intersect($tableNames, array_map('strtolower', $this->listTableNames()))); } /** * Return a list of all tables in the current database * * @return array */ public function listTableNames() { $sql = $this->_platform->getListTablesSQL(); $tables = $this->_conn->fetchAll($sql); return $this->_getPortableTablesList($tables); } /** * List the tables for this connection * * @return Table[] */ public function listTables() { $tableNames = $this->listTableNames(); $tables = array(); foreach ($tableNames AS $tableName) { $tables[] = $this->listTableDetails($tableName); } return $tables; } /** * @param string $tableName * @return Table */ public function listTableDetails($tableName) { $columns = $this->listTableColumns($tableName); $foreignKeys = array(); if ($this->_platform->supportsForeignKeyConstraints()) { $foreignKeys = $this->listTableForeignKeys($tableName); } $indexes = $this->listTableIndexes($tableName); return new Table($tableName, $columns, $indexes, $foreignKeys, false, array()); } /** * List the views this connection has * * @return View[] */ public function listViews() { $database = $this->_conn->getDatabase(); $sql = $this->_platform->getListViewsSQL($database); $views = $this->_conn->fetchAll($sql); return $this->_getPortableViewsList($views); } /** * List the foreign keys for the given table * * @param string $table The name of the table * @return ForeignKeyConstraint[] */ public function listTableForeignKeys($table, $database = null) { if (is_null($database)) { $database = $this->_conn->getDatabase(); } $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); $tableForeignKeys = $this->_conn->fetchAll($sql); return $this->_getPortableTableForeignKeysList($tableForeignKeys); } /* drop*() Methods */ /** * Drops a database. * * NOTE: You can not drop the database this SchemaManager is currently connected to. * * @param string $database The name of the database to drop */ public function dropDatabase($database) { $this->_execSql($this->_platform->getDropDatabaseSQL($database)); } /** * Drop the given table * * @param string $table The name of the table to drop */ public function dropTable($table) { $this->_execSql($this->_platform->getDropTableSQL($table)); } /** * Drop the index from the given table * * @param Index|string $index The name of the index * @param string|Table $table The name of the table */ public function dropIndex($index, $table) { if($index instanceof Index) { $index = $index->getName(); } $this->_execSql($this->_platform->getDropIndexSQL($index, $table)); } /** * Drop the constraint from the given table * * @param Constraint $constraint * @param string $table The name of the table */ public function dropConstraint(Constraint $constraint, $table) { $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table)); } /** * Drops a foreign key from a table. * * @param ForeignKeyConstraint|string $table The name of the table with the foreign key. * @param Table|string $name The name of the foreign key. * @return boolean $result */ public function dropForeignKey($foreignKey, $table) { $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table)); } /** * Drops a sequence with a given name. * * @param string $name The name of the sequence to drop. */ public function dropSequence($name) { $this->_execSql($this->_platform->getDropSequenceSQL($name)); } /** * Drop a view * * @param string $name The name of the view * @return boolean $result */ public function dropView($name) { $this->_execSql($this->_platform->getDropViewSQL($name)); } /* create*() Methods */ /** * Creates a new database. * * @param string $database The name of the database to create. */ public function createDatabase($database) { $this->_execSql($this->_platform->getCreateDatabaseSQL($database)); } /** * Create a new table. * * @param Table $table * @param int $createFlags */ public function createTable(Table $table) { $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS; $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); } /** * Create a new sequence * * @param Sequence $sequence * @throws Doctrine\DBAL\ConnectionException if something fails at database level */ public function createSequence($sequence) { $this->_execSql($this->_platform->getCreateSequenceSQL($sequence)); } /** * Create a constraint on a table * * @param Constraint $constraint * @param string|Table $table */ public function createConstraint(Constraint $constraint, $table) { $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table)); } /** * Create a new index on a table * * @param Index $index * @param string $table name of the table on which the index is to be created */ public function createIndex(Index $index, $table) { $this->_execSql($this->_platform->getCreateIndexSQL($index, $table)); } /** * Create a new foreign key * * @param ForeignKeyConstraint $foreignKey ForeignKey instance * @param string|Table $table name of the table on which the foreign key is to be created */ public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) { $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table)); } /** * Create a new view * * @param View $view */ public function createView(View $view) { $this->_execSql($this->_platform->getCreateViewSQL($view->getName(), $view->getSql())); } /* dropAndCreate*() Methods */ /** * Drop and create a constraint * * @param Constraint $constraint * @param string $table * @see dropConstraint() * @see createConstraint() */ public function dropAndCreateConstraint(Constraint $constraint, $table) { $this->tryMethod('dropConstraint', $constraint, $table); $this->createConstraint($constraint, $table); } /** * Drop and create a new index on a table * * @param string|Table $table name of the table on which the index is to be created * @param Index $index */ public function dropAndCreateIndex(Index $index, $table) { $this->tryMethod('dropIndex', $index->getName(), $table); $this->createIndex($index, $table); } /** * Drop and create a new foreign key * * @param ForeignKeyConstraint $foreignKey associative array that defines properties of the foreign key to be created. * @param string|Table $table name of the table on which the foreign key is to be created */ public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) { $this->tryMethod('dropForeignKey', $foreignKey, $table); $this->createForeignKey($foreignKey, $table); } /** * Drop and create a new sequence * * @param Sequence $sequence * @throws Doctrine\DBAL\ConnectionException if something fails at database level */ public function dropAndCreateSequence(Sequence $sequence) { $this->tryMethod('createSequence', $seqName, $start, $allocationSize); $this->createSequence($seqName, $start, $allocationSize); } /** * Drop and create a new table. * * @param Table $table */ public function dropAndCreateTable(Table $table) { $this->tryMethod('dropTable', $table->getName()); $this->createTable($table); } /** * Drop and creates a new database. * * @param string $database The name of the database to create. */ public function dropAndCreateDatabase($database) { $this->tryMethod('dropDatabase', $database); $this->createDatabase($database); } /** * Drop and create a new view * * @param View $view */ public function dropAndCreateView(View $view) { $this->tryMethod('dropView', $view->getName()); $this->createView($view); } /* alterTable() Methods */ /** * Alter an existing tables schema * * @param TableDiff $tableDiff */ public function alterTable(TableDiff $tableDiff) { $queries = $this->_platform->getAlterTableSQL($tableDiff); if (is_array($queries) && count($queries)) { foreach ($queries AS $ddlQuery) { $this->_execSql($ddlQuery); } } } /** * Rename a given table to another name * * @param string $name The current name of the table * @param string $newName The new name of the table */ public function renameTable($name, $newName) { $tableDiff = new TableDiff($name); $tableDiff->newName = $newName; $this->alterTable($tableDiff); } /** * Methods for filtering return values of list*() methods to convert * the native DBMS data definition to a portable Doctrine definition */ protected function _getPortableDatabasesList($databases) { $list = array(); foreach ($databases as $key => $value) { if ($value = $this->_getPortableDatabaseDefinition($value)) { $list[] = $value; } } return $list; } protected function _getPortableDatabaseDefinition($database) { return $database; } protected function _getPortableFunctionsList($functions) { $list = array(); foreach ($functions as $key => $value) { if ($value = $this->_getPortableFunctionDefinition($value)) { $list[] = $value; } } return $list; } protected function _getPortableFunctionDefinition($function) { return $function; } protected function _getPortableTriggersList($triggers) { $list = array(); foreach ($triggers as $key => $value) { if ($value = $this->_getPortableTriggerDefinition($value)) { $list[] = $value; } } return $list; } protected function _getPortableTriggerDefinition($trigger) { return $trigger; } protected function _getPortableSequencesList($sequences) { $list = array(); foreach ($sequences as $key => $value) { if ($value = $this->_getPortableSequenceDefinition($value)) { $list[] = $value; } } return $list; } /** * @param array $sequence * @return Sequence */ protected function _getPortableSequenceDefinition($sequence) { throw DBALException::notSupported('Sequences'); } /** * Independent of the database the keys of the column list result are lowercased. * * The name of the created column instance however is kept in its case. * * @param array $tableColumns * @return array */ protected function _getPortableTableColumnList($tableColumns) { $list = array(); foreach ($tableColumns as $key => $column) { if ($column = $this->_getPortableTableColumnDefinition($column)) { $name = strtolower($column->getName()); $list[$name] = $column; } } return $list; } /** * Get Table Column Definition * * @param array $tableColumn * @return Column */ abstract protected function _getPortableTableColumnDefinition($tableColumn); /** * Aggregate and group the index results according to the required data result. * * @param array $tableIndexRows * @param string $tableName * @return array */ protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null) { $result = array(); foreach($tableIndexRows AS $tableIndex) { $indexName = $keyName = $tableIndex['key_name']; if($tableIndex['primary']) { $keyName = 'primary'; } $keyName = strtolower($keyName); if(!isset($result[$keyName])) { $result[$keyName] = array( 'name' => $indexName, 'columns' => array($tableIndex['column_name']), 'unique' => $tableIndex['non_unique'] ? false : true, 'primary' => $tableIndex['primary'], ); } else { $result[$keyName]['columns'][] = $tableIndex['column_name']; } } $indexes = array(); foreach($result AS $indexKey => $data) { $indexes[$indexKey] = new Index($data['name'], $data['columns'], $data['unique'], $data['primary']); } return $indexes; } protected function _getPortableTablesList($tables) { $list = array(); foreach ($tables as $key => $value) { if ($value = $this->_getPortableTableDefinition($value)) { $list[] = $value; } } return $list; } protected function _getPortableTableDefinition($table) { return $table; } protected function _getPortableUsersList($users) { $list = array(); foreach ($users as $key => $value) { if ($value = $this->_getPortableUserDefinition($value)) { $list[] = $value; } } return $list; } protected function _getPortableUserDefinition($user) { return $user; } protected function _getPortableViewsList($views) { $list = array(); foreach ($views as $key => $value) { if ($view = $this->_getPortableViewDefinition($value)) { $viewName = strtolower($view->getName()); $list[$viewName] = $view; } } return $list; } protected function _getPortableViewDefinition($view) { return false; } protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = array(); foreach ($tableForeignKeys as $key => $value) { if ($value = $this->_getPortableTableForeignKeyDefinition($value)) { $list[] = $value; } } return $list; } protected function _getPortableTableForeignKeyDefinition($tableForeignKey) { return $tableForeignKey; } protected function _execSql($sql) { foreach ((array) $sql as $query) { $this->_conn->executeUpdate($query); } } /** * Create a schema instance for the current database. * * @return Schema */ public function createSchema() { $sequences = array(); if($this->_platform->supportsSequences()) { $sequences = $this->listSequences(); } $tables = $this->listTables(); return new Schema($tables, $sequences, $this->createSchemaConfig()); } /** * Create the configuration for this schema. * * @return SchemaConfig */ public function createSchemaConfig() { $schemaConfig = new SchemaConfig(); $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); return $schemaConfig; } }. */ namespace Doctrine\DBAL\Schema; /** * The abstract asset allows to reset the name of all assets without publishing this to the public userland. * * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ abstract class AbstractAsset { /** * @var string */ protected $_name; /** * Set name of this asset * * @param string $name */ protected function _setName($name) { $this->_name = $name; } /** * Return name of this schema asset. * * @return string */ public function getName() { return $this->_name; } /** * Generate an identifier from a list of column names obeying a certain string length. * * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, * however building idents automatically for foreign keys, composite keys or such can easily create * very long names. * * @param array $columnNames * @param string $postfix * @param int $maxSize * @return string */ protected function _generateIdentifierName($columnNames, $postfix='', $maxSize=30) { $columnCount = count($columnNames); $postfixLen = strlen($postfix); $parts = array_map(function($columnName) use($columnCount, $postfixLen, $maxSize) { return substr($columnName, -floor(($maxSize-$postfixLen)/$columnCount - 1)); }, $columnNames); $parts[] = $postfix; $identifier = trim(implode("_", $parts), '_'); // using implicit schema support of DB2 and Postgres there might be dots in the auto-generated // identifier names which can easily be replaced by underscores. $identifier = str_replace(".", "_", $identifier); if (is_numeric(substr($identifier, 0, 1))) { $identifier = "i" . substr($identifier, 0, strlen($identifier)-1); } return $identifier; } }. */ namespace Doctrine\DBAL\Schema; /** * Configuration for a Schema * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class SchemaConfig { /** * @var bool */ protected $_hasExplicitForeignKeyIndexes = false; /** * @var int */ protected $_maxIdentifierLength = 63; /** * @return bool */ public function hasExplicitForeignKeyIndexes() { return $this->_hasExplicitForeignKeyIndexes; } /** * @param bool $flag */ public function setExplicitForeignKeyIndexes($flag) { $this->_hasExplicitForeignKeyIndexes = (bool)$flag; } /** * @param int $length */ public function setMaxIdentifierLength($length) { $this->_maxIdentifierLength = (int)$length; } /** * @return int */ public function getMaxIdentifierLength() { return $this->_maxIdentifierLength; } }. */ namespace Doctrine\DBAL\Schema; use \Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Schema\Visitor\Visitor; /** * Object representation of a database column * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class Column extends AbstractAsset { /** * @var \Doctrine\DBAL\Types\Type */ protected $_type; /** * @var int */ protected $_length = null; /** * @var int */ protected $_precision = 0; /** * @var int */ protected $_scale = 0; /** * @var bool */ protected $_unsigned = false; /** * @var bool */ protected $_fixed = false; /** * @var bool */ protected $_notnull = true; /** * @var string */ protected $_default = null; /** * @var bool */ protected $_autoincrement = false; /** * @var array */ protected $_platformOptions = array(); /** * @var string */ protected $_columnDefinition = null; /** * Create a new Column * * @param string $columnName * @param Doctrine\DBAL\Types\Type $type * @param int $length * @param bool $notNull * @param mixed $default * @param bool $unsigned * @param bool $fixed * @param int $precision * @param int $scale * @param array $platformOptions */ public function __construct($columnName, Type $type, array $options=array()) { $this->_setName($columnName); $this->setType($type); $this->setOptions($options); } /** * @param array $options * @return Column */ public function setOptions(array $options) { foreach ($options AS $name => $value) { $method = "set".$name; if (method_exists($this, $method)) { $this->$method($value); } } return $this; } /** * @param Type $type * @return Column */ public function setType(Type $type) { $this->_type = $type; return $this; } /** * @param int $length * @return Column */ public function setLength($length) { if($length !== null) { $this->_length = (int)$length; } else { $this->_length = null; } return $this; } /** * @param int $precision * @return Column */ public function setPrecision($precision) { $this->_precision = (int)$precision; return $this; } /** * @param int $scale * @return Column */ public function setScale($scale) { $this->_scale = $scale; return $this; } /** * * @param bool $unsigned * @return Column */ public function setUnsigned($unsigned) { $this->_unsigned = (bool)$unsigned; return $this; } /** * * @param bool $fixed * @return Column */ public function setFixed($fixed) { $this->_fixed = (bool)$fixed; return $this; } /** * @param bool $notnull * @return Column */ public function setNotnull($notnull) { $this->_notnull = (bool)$notnull; return $this; } /** * * @param mixed $default * @return Column */ public function setDefault($default) { $this->_default = $default; return $this; } /** * * @param array $platformOptions * @return Column */ public function setPlatformOptions(array $platformOptions) { $this->_platformOptions = $platformOptions; return $this; } /** * * @param string $name * @param mixed $value * @return Column */ public function setPlatformOption($name, $value) { $this->_platformOptions[$name] = $value; return $this; } /** * * @param string * @return Column */ public function setColumnDefinition($value) { $this->_columnDefinition = $value; return $this; } public function getType() { return $this->_type; } public function getLength() { return $this->_length; } public function getPrecision() { return $this->_precision; } public function getScale() { return $this->_scale; } public function getUnsigned() { return $this->_unsigned; } public function getFixed() { return $this->_fixed; } public function getNotnull() { return $this->_notnull; } public function getDefault() { return $this->_default; } public function getPlatformOptions() { return $this->_platformOptions; } public function hasPlatformOption($name) { return isset($this->_platformOptions[$name]); } public function getPlatformOption($name) { return $this->_platformOptions[$name]; } public function getColumnDefinition() { return $this->_columnDefinition; } public function getAutoincrement() { return $this->_autoincrement; } public function setAutoincrement($flag) { $this->_autoincrement = $flag; return $this; } /** * @param Visitor $visitor */ public function visit(\Doctrine\DBAL\Schema\Visitor $visitor) { $visitor->accept($this); } /** * @return array */ public function toArray() { return array_merge(array( 'name' => $this->_name, 'type' => $this->_type, 'default' => $this->_default, 'notnull' => $this->_notnull, 'length' => $this->_length, 'precision' => $this->_precision, 'scale' => $this->_scale, 'fixed' => $this->_fixed, 'unsigned' => $this->_unsigned, 'autoincrement' => $this->_autoincrement, 'columnDefinition' => $this->_columnDefinition, ), $this->_platformOptions); } }. */ namespace Doctrine\DBAL\Schema; /** * Marker interface for contraints * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ interface Constraint { public function getName(); public function getColumns(); }. */ namespace Doctrine\DBAL\Schema; use \Doctrine\DBAL\Platforms\AbstractPlatform; /** * Schema Diff * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. * @license http://ez.no/licenses/new_bsd New BSD License * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class SchemaDiff { /** * All added tables * * @var array(string=>ezcDbSchemaTable) */ public $newTables = array(); /** * All changed tables * * @var array(string=>ezcDbSchemaTableDiff) */ public $changedTables = array(); /** * All removed tables * * @var array(string=>Table) */ public $removedTables = array(); /** * @var array */ public $newSequences = array(); /** * @var array */ public $changedSequences = array(); /** * @var array */ public $removedSequences = array(); /** * @var array */ public $orphanedForeignKeys = array(); /** * Constructs an SchemaDiff object. * * @param array(string=>Table) $newTables * @param array(string=>TableDiff) $changedTables * @param array(string=>bool) $removedTables */ public function __construct($newTables = array(), $changedTables = array(), $removedTables = array()) { $this->newTables = $newTables; $this->changedTables = $changedTables; $this->removedTables = $removedTables; } /** * The to save sql mode ensures that the following things don't happen: * * 1. Tables are deleted * 2. Sequences are deleted * 3. Foreign Keys which reference tables that would otherwise be deleted. * * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. * * @param AbstractPlatform $platform * @return array */ public function toSaveSql(AbstractPlatform $platform) { return $this->_toSql($platform, true); } /** * @param AbstractPlatform $platform * @return array */ public function toSql(AbstractPlatform $platform) { return $this->_toSql($platform, false); } /** * @param AbstractPlatform $platform * @param bool $saveMode * @return array */ protected function _toSql(AbstractPlatform $platform, $saveMode = false) { $sql = array(); if ($platform->supportsForeignKeyConstraints() && $saveMode == false) { foreach ($this->orphanedForeignKeys AS $orphanedForeignKey) { $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTableName()); } } if ($platform->supportsSequences() == true) { foreach ($this->changedSequences AS $sequence) { $sql[] = $platform->getDropSequenceSQL($sequence); $sql[] = $platform->getCreateSequenceSQL($sequence); } if ($saveMode === false) { foreach ($this->removedSequences AS $sequence) { $sql[] = $platform->getDropSequenceSQL($sequence); } } foreach ($this->newSequences AS $sequence) { $sql[] = $platform->getCreateSequenceSQL($sequence); } } $foreignKeySql = array(); foreach ($this->newTables AS $table) { $sql = array_merge( $sql, $platform->getCreateTableSQL($table, AbstractPlatform::CREATE_INDEXES) ); if ($platform->supportsForeignKeyConstraints()) { foreach ($table->getForeignKeys() AS $foreignKey) { $foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table); } } } $sql = array_merge($sql, $foreignKeySql); if ($saveMode === false) { foreach ($this->removedTables AS $table) { $sql[] = $platform->getDropTableSQL($table); } } foreach ($this->changedTables AS $tableDiff) { $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); } return $sql; } }. */ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector; use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; use Doctrine\DBAL\Schema\Visitor\Visitor; /** * Object representation of a database schema * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class Schema extends AbstractAsset { /** * @var array */ protected $_tables = array(); /** * @var array */ protected $_sequences = array(); /** * @var SchemaConfig */ protected $_schemaConfig = false; /** * @param array $tables * @param array $sequences * @param array $views * @param array $triggers * @param SchemaConfig $schemaConfig */ public function __construct(array $tables=array(), array $sequences=array(), SchemaConfig $schemaConfig=null) { if ($schemaConfig == null) { $schemaConfig = new SchemaConfig(); } $this->_schemaConfig = $schemaConfig; foreach ($tables AS $table) { $this->_addTable($table); } foreach ($sequences AS $sequence) { $this->_addSequence($sequence); } } /** * @return bool */ public function hasExplicitForeignKeyIndexes() { return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); } /** * @param Table $table */ protected function _addTable(Table $table) { $tableName = strtolower($table->getName()); if(isset($this->_tables[$tableName])) { throw SchemaException::tableAlreadyExists($tableName); } $this->_tables[$tableName] = $table; $table->setSchemaConfig($this->_schemaConfig); } /** * @param Sequence $sequence */ protected function _addSequence(Sequence $sequence) { $seqName = strtolower($sequence->getName()); if (isset($this->_sequences[$seqName])) { throw SchemaException::sequenceAlreadyExists($seqName); } $this->_sequences[$seqName] = $sequence; } /** * Get all tables of this schema. * * @return array */ public function getTables() { return $this->_tables; } /** * @param string $tableName * @return Table */ public function getTable($tableName) { $tableName = strtolower($tableName); if (!isset($this->_tables[$tableName])) { throw SchemaException::tableDoesNotExist($tableName); } return $this->_tables[$tableName]; } /** * Does this schema have a table with the given name? * * @param string $tableName * @return Schema */ public function hasTable($tableName) { $tableName = strtolower($tableName); return isset($this->_tables[$tableName]); } /** * @param string $sequenceName * @return bool */ public function hasSequence($sequenceName) { $sequenceName = strtolower($sequenceName); return isset($this->_sequences[$sequenceName]); } /** * @throws SchemaException * @param string $sequenceName * @return Doctrine\DBAL\Schema\Sequence */ public function getSequence($sequenceName) { $sequenceName = strtolower($sequenceName); if(!$this->hasSequence($sequenceName)) { throw SchemaException::sequenceDoesNotExist($sequenceName); } return $this->_sequences[$sequenceName]; } /** * @return Doctrine\DBAL\Schema\Sequence[] */ public function getSequences() { return $this->_sequences; } /** * Create a new table * * @param string $tableName * @return Table */ public function createTable($tableName) { $table = new Table($tableName); $this->_addTable($table); return $table; } /** * Rename a table * * @param string $oldTableName * @param string $newTableName * @return Schema */ public function renameTable($oldTableName, $newTableName) { $table = $this->getTable($oldTableName); $table->_setName($newTableName); $this->dropTable($oldTableName); $this->_addTable($table); return $this; } /** * Drop a table from the schema. * * @param string $tableName * @return Schema */ public function dropTable($tableName) { $tableName = strtolower($tableName); $table = $this->getTable($tableName); unset($this->_tables[$tableName]); return $this; } /** * Create a new sequence * * @param string $sequenceName * @param int $allocationSize * @param int $initialValue * @return Sequence */ public function createSequence($sequenceName, $allocationSize=1, $initialValue=1) { $seq = new Sequence($sequenceName, $allocationSize, $initialValue); $this->_addSequence($seq); return $seq; } /** * @param string $sequenceName * @return Schema */ public function dropSequence($sequenceName) { $sequenceName = strtolower($sequenceName); unset($this->_sequences[$sequenceName]); return $this; } /** * Return an array of necessary sql queries to create the schema on the given platform. * * @param AbstractPlatform $platform * @return array */ public function toSql(\Doctrine\DBAL\Platforms\AbstractPlatform $platform) { $sqlCollector = new CreateSchemaSqlCollector($platform); $this->visit($sqlCollector); return $sqlCollector->getQueries(); } /** * Return an array of necessary sql queries to drop the schema on the given platform. * * @param AbstractPlatform $platform * @return array */ public function toDropSql(\Doctrine\DBAL\Platforms\AbstractPlatform $platform) { $dropSqlCollector = new DropSchemaSqlCollector($platform); $this->visit($dropSqlCollector); return $dropSqlCollector->getQueries(); } /** * @param Schema $toSchema * @param AbstractPlatform $platform */ public function getMigrateToSql(Schema $toSchema, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { $comparator = new Comparator(); $schemaDiff = $comparator->compare($this, $toSchema); return $schemaDiff->toSql($platform); } /** * @param Schema $fromSchema * @param AbstractPlatform $platform */ public function getMigrateFromSql(Schema $fromSchema, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { $comparator = new Comparator(); $schemaDiff = $comparator->compare($fromSchema, $this); return $schemaDiff->toSql($platform); } /** * @param Visitor $visitor */ public function visit(Visitor $visitor) { $visitor->acceptSchema($this); foreach ($this->_tables AS $table) { $table->visit($visitor); } foreach ($this->_sequences AS $sequence) { $sequence->visit($visitor); } } /** * Cloning a Schema triggers a deep clone of all related assets. * * @return void */ public function __clone() { foreach ($this->_tables AS $k => $table) { $this->_tables[$k] = clone $table; } foreach ($this->_sequences AS $k => $sequence) { $this->_sequences[$k] = clone $sequence; } } } . */ namespace Doctrine\DBAL\Schema; /** * xxx * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Juozas Kaziukenas * @version $Revision$ * @since 2.0 */ class MsSqlSchemaManager extends AbstractSchemaManager { /** * @override */ protected function _getPortableTableColumnDefinition($tableColumn) { $dbType = strtolower($tableColumn['TYPE_NAME']); $autoincrement = false; if (stripos($dbType, 'identity')) { $dbType = trim(str_ireplace('identity', '', $dbType)); $autoincrement = true; } $type = array(); $unsigned = $fixed = null; if (!isset($tableColumn['name'])) { $tableColumn['name'] = ''; } $default = $tableColumn['COLUMN_DEF']; while ($default != ($default2 = preg_replace("/^\((.*)\)$/", '$1', $default))) { $default = $default2; } $length = (int) $tableColumn['LENGTH']; $type = $this->_platform->getDoctrineTypeMapping($dbType); switch ($type) { case 'char': if ($tableColumn['LENGTH'] == '1') { $type = 'boolean'; if (preg_match('/^(is|has)/', $tableColumn['name'])) { $type = array_reverse($type); } } $fixed = true; break; case 'text': $fixed = false; break; } switch ($dbType) { case 'nchar': case 'nvarchar': case 'ntext': // Unicode data requires 2 bytes per character $length = $length / 2; break; } $options = array( 'length' => ($length == 0 || !in_array($type, array('text', 'string'))) ? null : $length, 'unsigned' => (bool) $unsigned, 'fixed' => (bool) $fixed, 'default' => $default !== 'NULL' ? $default : null, 'notnull' => (bool) ($tableColumn['IS_NULLABLE'] != 'YES'), 'scale' => $tableColumn['SCALE'], 'precision' => $tableColumn['PRECISION'], 'autoincrement' => $autoincrement, ); return new Column($tableColumn['COLUMN_NAME'], \Doctrine\DBAL\Types\Type::getType($type), $options); } /** * @override */ protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null) { $result = array(); foreach ($tableIndexRows AS $tableIndex) { $indexName = $keyName = $tableIndex['index_name']; if (strpos($tableIndex['index_description'], 'primary key') !== false) { $keyName = 'primary'; } $keyName = strtolower($keyName); $result[$keyName] = array( 'name' => $indexName, 'columns' => explode(', ', $tableIndex['index_keys']), 'unique' => strpos($tableIndex['index_description'], 'unique') !== false, 'primary' => strpos($tableIndex['index_description'], 'primary key') !== false, ); } $indexes = array(); foreach ($result AS $indexKey => $data) { $indexes[$indexKey] = new Index($data['name'], $data['columns'], $data['unique'], $data['primary']); } return $indexes; } /** * @override */ public function _getPortableTableForeignKeyDefinition($tableForeignKey) { return new ForeignKeyConstraint( (array) $tableForeignKey['ColumnName'], $tableForeignKey['ReferenceTableName'], (array) $tableForeignKey['ReferenceColumnName'], $tableForeignKey['ForeignKey'], array( 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']), ) ); } /** * @override */ protected function _getPortableTableDefinition($table) { return $table['name']; } /** * @override */ protected function _getPortableDatabaseDefinition($database) { return $database['name']; } /** * @override */ protected function _getPortableViewDefinition($view) { // @todo return new View($view['name'], null); } }. */ namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform, Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Schema, Doctrine\DBAL\Schema\Column, Doctrine\DBAL\Schema\ForeignKeyConstraint, Doctrine\DBAL\Schema\Constraint, Doctrine\DBAL\Schema\Sequence, Doctrine\DBAL\Schema\Index; /** * Schema Visitor used for Validation or Generation purposes. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ interface Visitor { /** * @param Schema $schema */ public function acceptSchema(Schema $schema); /** * @param Table $table */ public function acceptTable(Table $table); /** * @param Column $column */ public function acceptColumn(Table $table, Column $column); /** * @param Table $localTable * @param ForeignKeyConstraint $fkConstraint */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint); /** * @param Table $table * @param Index $index */ public function acceptIndex(Table $table, Index $index); /** * @param Sequence $sequence */ public function acceptSequence(Sequence $sequence); }. */ namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform, Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Schema, Doctrine\DBAL\Schema\Column, Doctrine\DBAL\Schema\ForeignKeyConstraint, Doctrine\DBAL\Schema\Constraint, Doctrine\DBAL\Schema\Sequence, Doctrine\DBAL\Schema\Index; class CreateSchemaSqlCollector implements Visitor { /** * @var array */ private $_createTableQueries = array(); /** * @var array */ private $_createSequenceQueries = array(); /** * @var array */ private $_createFkConstraintQueries = array(); /** * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $_platform = null; /** * @param AbstractPlatform $platform */ public function __construct(AbstractPlatform $platform) { $this->_platform = $platform; } /** * @param Schema $schema */ public function acceptSchema(Schema $schema) { } /** * Generate DDL Statements to create the accepted table with all its dependencies. * * @param Table $table */ public function acceptTable(Table $table) { $this->_createTableQueries = array_merge($this->_createTableQueries, $this->_platform->getCreateTableSQL($table) ); } public function acceptColumn(Table $table, Column $column) { } /** * @param Table $localTable * @param ForeignKeyConstraint $fkConstraint */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { // Append the foreign key constraints SQL if ($this->_platform->supportsForeignKeyConstraints()) { $this->_createFkConstraintQueries = array_merge($this->_createFkConstraintQueries, (array) $this->_platform->getCreateForeignKeySQL($fkConstraint, $localTable->getName()) ); } } /** * @param Table $table * @param Index $index */ public function acceptIndex(Table $table, Index $index) { } /** * @param Sequence $sequence */ public function acceptSequence(Sequence $sequence) { $this->_createSequenceQueries = array_merge( $this->_createSequenceQueries, (array)$this->_platform->getCreateSequenceSQL($sequence) ); } /** * @return array */ public function resetQueries() { $this->_createTableQueries = array(); $this->_createSequenceQueries = array(); $this->_createFkConstraintQueries = array(); } /** * Get all queries collected so far. * * @return array */ public function getQueries() { return array_merge( $this->_createTableQueries, $this->_createSequenceQueries, $this->_createFkConstraintQueries ); } }. */ namespace Doctrine\DBAL\Schema\Visitor; use Doctrine\DBAL\Platforms\AbstractPlatform, Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Schema, Doctrine\DBAL\Schema\Column, Doctrine\DBAL\Schema\ForeignKeyConstraint, Doctrine\DBAL\Schema\Constraint, Doctrine\DBAL\Schema\Sequence, Doctrine\DBAL\Schema\Index; /** * Gather SQL statements that allow to completly drop the current schema. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class DropSchemaSqlCollector implements Visitor { /** * @var array */ private $_constraints = array(); /** * @var array */ private $_sequences = array(); /** * @var array */ private $_tables = array(); /** * * @var \Doctrine\DBAL\Platforms\AbstractPlatform */ private $_platform = null; /** * @param AbstractPlatform $platform */ public function __construct(AbstractPlatform $platform) { $this->_platform = $platform; } /** * @param Schema $schema */ public function acceptSchema(Schema $schema) { } /** * @param Table $table */ public function acceptTable(Table $table) { $this->_tables[] = $this->_platform->getDropTableSQL($table->getName()); } /** * @param Column $column */ public function acceptColumn(Table $table, Column $column) { } /** * @param Table $localTable * @param ForeignKeyConstraint $fkConstraint */ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) { if (strlen($fkConstraint->getName()) == 0) { throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); } $this->_constraints[] = $this->_platform->getDropForeignKeySQL($fkConstraint->getName(), $localTable->getName()); } /** * @param Table $table * @param Index $index */ public function acceptIndex(Table $table, Index $index) { } /** * @param Sequence $sequence */ public function acceptSequence(Sequence $sequence) { $this->_sequences[] = $this->_platform->getDropSequenceSQL($sequence->getName()); } /** * @return array */ public function clearQueries() { $this->_constraints = $this->_sequences = $this->_tables = array(); } /** * @return array */ public function getQueries() { return array_merge($this->_constraints, $this->_tables, $this->_sequences); } } . */ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\Visitor; class ForeignKeyConstraint extends AbstractAsset implements Constraint { /** * @var Table */ protected $_localTable; /** * @var array */ protected $_localColumnNames; /** * @var string */ protected $_foreignTableName; /** * @var array */ protected $_foreignColumnNames; /** * @var string */ protected $_cascade = ''; /** * @var array */ protected $_options; /** * * @param array $localColumnNames * @param string $foreignTableName * @param array $foreignColumnNames * @param string $cascade * @param string|null $name */ public function __construct(array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name=null, array $options=array()) { $this->_setName($name); $this->_localColumnNames = $localColumnNames; $this->_foreignTableName = $foreignTableName; $this->_foreignColumnNames = $foreignColumnNames; $this->_options = $options; } /** * @return string */ public function getLocalTableName() { return $this->_localTable->getName(); } /** * @param Table $table */ public function setLocalTable(Table $table) { $this->_localTable = $table; } /** * @return array */ public function getLocalColumns() { return $this->_localColumnNames; } public function getColumns() { return $this->_localColumnNames; } /** * @return string */ public function getForeignTableName() { return $this->_foreignTableName; } /** * @return array */ public function getForeignColumns() { return $this->_foreignColumnNames; } public function hasOption($name) { return isset($this->_options[$name]); } public function getOption($name) { return $this->_options[$name]; } /** * Foreign Key onUpdate status * * @return string|null */ public function onUpdate() { return $this->_onEvent('onUpdate'); } /** * Foreign Key onDelete status * * @return string|null */ public function onDelete() { return $this->_onEvent('onDelete'); } /** * @param string $event * @return string|null */ private function _onEvent($event) { if (isset($this->_options[$event])) { $onEvent = strtoupper($this->_options[$event]); if (!in_array($onEvent, array('NO ACTION', 'RESTRICT'))) { return $onEvent; } } return false; } } . */ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Schema\Visitor\Visitor; use Doctrine\DBAL\DBALException; /** * Object Representation of a table * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class Table extends AbstractAsset { /** * @var string */ protected $_name = null; /** * @var array */ protected $_columns = array(); /** * @var array */ protected $_indexes = array(); /** * @var string */ protected $_primaryKeyName = false; /** * @var array */ protected $_fkConstraints = array(); /** * @var array */ protected $_options = array(); /** * @var SchemaConfig */ protected $_schemaConfig = null; /** * * @param string $tableName * @param array $columns * @param array $indexes * @param array $fkConstraints * @param int $idGeneratorType * @param array $options */ public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array()) { if (strlen($tableName) == 0) { throw DBALException::invalidTableName($tableName); } $this->_setName($tableName); $this->_idGeneratorType = $idGeneratorType; foreach ($columns AS $column) { $this->_addColumn($column); } foreach ($indexes AS $idx) { $this->_addIndex($idx); } foreach ($fkConstraints AS $constraint) { $this->_addForeignKeyConstraint($constraint); } $this->_options = $options; } /** * @param SchemaConfig $schemaConfig */ public function setSchemaConfig(SchemaConfig $schemaConfig) { $this->_schemaConfig = $schemaConfig; } /** * @return int */ protected function _getMaxIdentifierLength() { if ($this->_schemaConfig instanceof SchemaConfig) { return $this->_schemaConfig->getMaxIdentifierLength(); } else { return 63; } } /** * Set Primary Key * * @param array $columns * @param string $indexName * @return Table */ public function setPrimaryKey(array $columns, $indexName = false) { $primaryKey = $this->_createIndex($columns, $indexName ?: "primary", true, true); foreach ($columns AS $columnName) { $column = $this->getColumn($columnName); $column->setNotnull(true); } return $primaryKey; } /** * @param array $columnNames * @param string $indexName * @return Table */ public function addIndex(array $columnNames, $indexName = null) { if($indexName == null) { $indexName = $this->_generateIdentifierName( array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength() ); } return $this->_createIndex($columnNames, $indexName, false, false); } /** * * @param array $columnNames * @param string $indexName * @return Table */ public function addUniqueIndex(array $columnNames, $indexName = null) { if ($indexName == null) { $indexName = $this->_generateIdentifierName( array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength() ); } return $this->_createIndex($columnNames, $indexName, true, false); } /** * Check if an index begins in the order of the given columns. * * @param array $columnsNames * @return bool */ public function columnsAreIndexed(array $columnsNames) { foreach ($this->getIndexes() AS $index) { /* @var $index Index */ if ($index->spansColumns($columnsNames)) { return true; } } return false; } /** * * @param array $columnNames * @param string $indexName * @param bool $isUnique * @param bool $isPrimary * @return Table */ private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary) { if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) { throw SchemaException::indexNameInvalid($indexName); } foreach ($columnNames AS $columnName => $indexColOptions) { if (is_numeric($columnName) && is_string($indexColOptions)) { $columnName = $indexColOptions; } if ( ! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } $this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary)); return $this; } /** * @param string $columnName * @param string $columnType * @param array $options * @return Column */ public function addColumn($columnName, $typeName, array $options=array()) { $column = new Column($columnName, Type::getType($typeName), $options); $this->_addColumn($column); return $column; } /** * Rename Column * * @param string $oldColumnName * @param string $newColumnName * @return Table */ public function renameColumn($oldColumnName, $newColumnName) { $column = $this->getColumn($oldColumnName); $this->dropColumn($oldColumnName); $column->_setName($newColumnName); return $this; } /** * Change Column Details * * @param string $columnName * @param array $options * @return Table */ public function changeColumn($columnName, array $options) { $column = $this->getColumn($columnName); $column->setOptions($options); return $this; } /** * Drop Column from Table * * @param string $columnName * @return Table */ public function dropColumn($columnName) { $columnName = strtolower($columnName); $column = $this->getColumn($columnName); unset($this->_columns[$columnName]); return $this; } /** * Add a foreign key constraint * * Name is inferred from the local columns * * @param Table $foreignTable * @param array $localColumns * @param array $foreignColumns * @param array $options * @return Table */ public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) { $name = $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength()); return $this->addNamedForeignKeyConstraint($name, $foreignTable, $localColumnNames, $foreignColumnNames, $options); } /** * Add a foreign key constraint * * Name is to be generated by the database itsself. * * @param Table $foreignTable * @param array $localColumns * @param array $foreignColumns * @param array $options * @return Table */ public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) { return $this->addNamedForeignKeyConstraint(null, $foreignTable, $localColumnNames, $foreignColumnNames, $options); } /** * Add a foreign key constraint with a given name * * @param string $name * @param Table $foreignTable * @param array $localColumns * @param array $foreignColumns * @param array $options * @return Table */ public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) { if ($foreignTable instanceof Table) { $foreignTableName = $foreignTable->getName(); foreach ($foreignColumnNames AS $columnName) { if ( ! $foreignTable->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); } } } else { $foreignTableName = $foreignTable; } foreach ($localColumnNames AS $columnName) { if ( ! $this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } } $constraint = new ForeignKeyConstraint( $localColumnNames, $foreignTableName, $foreignColumnNames, $name, $options ); $this->_addForeignKeyConstraint($constraint); return $this; } /** * @param string $name * @param string $value * @return Table */ public function addOption($name, $value) { $this->_options[$name] = $value; return $this; } /** * @param Column $column */ protected function _addColumn(Column $column) { $columnName = $column->getName(); $columnName = strtolower($columnName); if (isset($this->_columns[$columnName])) { throw SchemaException::columnAlreadyExists($this->getName(), $columnName); } $this->_columns[$columnName] = $column; } /** * Add index to table * * @param Index $indexCandidate * @return Table */ protected function _addIndex(Index $indexCandidate) { // check for duplicates foreach ($this->_indexes AS $existingIndex) { if ($indexCandidate->isFullfilledBy($existingIndex)) { return $this; } } $indexName = $indexCandidate->getName(); $indexName = strtolower($indexName); if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) { throw SchemaException::indexAlreadyExists($indexName, $this->_name); } // remove overruled indexes foreach ($this->_indexes AS $idxKey => $existingIndex) { if ($indexCandidate->overrules($existingIndex)) { unset($this->_indexes[$idxKey]); } } if ($indexCandidate->isPrimary()) { $this->_primaryKeyName = $indexName; } $this->_indexes[$indexName] = $indexCandidate; return $this; } /** * @param ForeignKeyConstraint $constraint */ protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) { $constraint->setLocalTable($this); if(strlen($constraint->getName())) { $name = $constraint->getName(); } else { $name = $this->_generateIdentifierName( array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength() ); } $name = strtolower($name); $this->_fkConstraints[$name] = $constraint; // add an explicit index on the foreign key columns. If there is already an index that fullfils this requirements drop the request. // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes // lead to duplicates. This creates compuation overhead in this case, however no duplicate indexes are ever added (based on columns). $this->addIndex($constraint->getColumns()); } /** * Does Table have a foreign key constraint with the given name? * * * @param string $constraintName * @return bool */ public function hasForeignKey($constraintName) { $constraintName = strtolower($constraintName); return isset($this->_fkConstraints[$constraintName]); } /** * @param string $constraintName * @return ForeignKeyConstraint */ public function getForeignKey($constraintName) { $constraintName = strtolower($constraintName); if(!$this->hasForeignKey($constraintName)) { throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); } return $this->_fkConstraints[$constraintName]; } /** * @return Column[] */ public function getColumns() { $columns = $this->_columns; $pkCols = array(); $fkCols = array(); if ($this->hasIndex($this->_primaryKeyName)) { $pkCols = $this->getPrimaryKey()->getColumns(); } foreach ($this->getForeignKeys() AS $fk) { /* @var $fk ForeignKeyConstraint */ $fkCols = array_merge($fkCols, $fk->getColumns()); } $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns))); uksort($columns, function($a, $b) use($colNames) { return (array_search($a, $colNames) >= array_search($b, $colNames)); }); return $columns; } /** * Does this table have a column with the given name? * * @param string $columnName * @return bool */ public function hasColumn($columnName) { $columnName = strtolower($columnName); return isset($this->_columns[$columnName]); } /** * Get a column instance * * @param string $columnName * @return Column */ public function getColumn($columnName) { $columnName = strtolower($columnName); if (!$this->hasColumn($columnName)) { throw SchemaException::columnDoesNotExist($columnName, $this->_name); } return $this->_columns[$columnName]; } /** * @return Index */ public function getPrimaryKey() { return $this->getIndex($this->_primaryKeyName); } /** * @param string $indexName * @return bool */ public function hasIndex($indexName) { $indexName = strtolower($indexName); return (isset($this->_indexes[$indexName])); } /** * @param string $indexName * @return Index */ public function getIndex($indexName) { $indexName = strtolower($indexName); if (!$this->hasIndex($indexName)) { throw SchemaException::indexDoesNotExist($indexName, $this->_name); } return $this->_indexes[$indexName]; } /** * @return array */ public function getIndexes() { return $this->_indexes; } /** * Get Constraints * * @return array */ public function getForeignKeys() { return $this->_fkConstraints; } public function hasOption($name) { return isset($this->_options[$name]); } public function getOption($name) { return $this->_options[$name]; } public function getOptions() { return $this->_options; } /** * @param Visitor $visitor */ public function visit(Visitor $visitor) { $visitor->acceptTable($this); foreach ($this->getColumns() AS $column) { $visitor->acceptColumn($this, $column); } foreach ($this->getIndexes() AS $index) { $visitor->acceptIndex($this, $index); } foreach ($this->getForeignKeys() AS $constraint) { $visitor->acceptForeignKey($this, $constraint); } } /** * Clone of a Table triggers a deep clone of all affected assets */ public function __clone() { foreach ($this->_columns AS $k => $column) { $this->_columns[$k] = clone $column; } foreach ($this->_indexes AS $k => $index) { $this->_indexes[$k] = clone $index; } foreach ($this->_fkConstraints AS $k => $fk) { $this->_fkConstraints[$k] = clone $fk; $this->_fkConstraints[$k]->setLocalTable($this); } } }. */ namespace Doctrine\DBAL\Schema; /** * Table Diff * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. * @license http://ez.no/licenses/new_bsd New BSD License * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class TableDiff { /** * @var string */ public $name = null; /** * @var string */ public $newName = false; /** * All added fields * * @var array(string=>Column) */ public $addedColumns; /** * All changed fields * * @var array(string=>Column) */ public $changedColumns = array(); /** * All removed fields * * @var array(string=>bool) */ public $removedColumns = array(); /** * Columns that are only renamed from key to column instance name. * * @var array(string=>Column) */ public $renamedColumns = array(); /** * All added indexes * * @var array(string=>Index) */ public $addedIndexes = array(); /** * All changed indexes * * @var array(string=>Index) */ public $changedIndexes = array(); /** * All removed indexes * * @var array(string=>bool) */ public $removedIndexes = array(); /** * All added foreign key definitions * * @var array */ public $addedForeignKeys = array(); /** * All changed foreign keys * * @var array */ public $changedForeignKeys = array(); /** * All removed foreign keys * * @var array */ public $removedForeignKeys = array(); /** * Constructs an TableDiff object. * * @param array(string=>Column) $addedColumns * @param array(string=>Column) $changedColumns * @param array(string=>bool) $removedColumns * @param array(string=>Index) $addedIndexes * @param array(string=>Index) $changedIndexes * @param array(string=>bool) $removedIndexes */ public function __construct($tableName, $addedColumns = array(), $changedColumns = array(), $removedColumns = array(), $addedIndexes = array(), $changedIndexes = array(), $removedIndexes = array()) { $this->name = $tableName; $this->addedColumns = $addedColumns; $this->changedColumns = $changedColumns; $this->removedColumns = $removedColumns; $this->addedIndexes = $addedIndexes; $this->changedIndexes = $changedIndexes; $this->removedIndexes = $removedIndexes; } }. */ namespace Doctrine\DBAL\Schema; /** * Oracle Schema Manager * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @author Benjamin Eberlei * @version $Revision$ * @since 2.0 */ class OracleSchemaManager extends AbstractSchemaManager { protected function _getPortableViewDefinition($view) { $view = \array_change_key_case($view, CASE_LOWER); return new View($view['view_name'], $view['text']); } protected function _getPortableUserDefinition($user) { $user = \array_change_key_case($user, CASE_LOWER); return array( 'user' => $user['username'], ); } protected function _getPortableTableDefinition($table) { $table = \array_change_key_case($table, CASE_LOWER); return $table['table_name']; } /** * @license New BSD License * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html * @param array $tableIndexes * @param string $tableName * @return array */ protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) { $indexBuffer = array(); foreach ( $tableIndexes as $tableIndex ) { $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER); $keyName = strtolower($tableIndex['name']); if ( strtolower($tableIndex['is_primary']) == "p" ) { $keyName = 'primary'; $buffer['primary'] = true; $buffer['non_unique'] = false; } else { $buffer['primary'] = false; $buffer['non_unique'] = ( $tableIndex['is_unique'] == 0 ) ? true : false; } $buffer['key_name'] = $keyName; $buffer['column_name'] = $tableIndex['column_name']; $indexBuffer[] = $buffer; } return parent::_getPortableTableIndexesList($indexBuffer, $tableName); } protected function _getPortableTableColumnDefinition($tableColumn) { $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER); $dbType = strtolower($tableColumn['data_type']); if(strpos($dbType, "timestamp(") === 0) { if (strpos($dbType, "WITH TIME ZONE")) { $dbType = "timestamptz"; } else { $dbType = "timestamp"; } } $type = array(); $length = $unsigned = $fixed = null; if ( ! empty($tableColumn['data_length'])) { $length = $tableColumn['data_length']; } if ( ! isset($tableColumn['column_name'])) { $tableColumn['column_name'] = ''; } if (stripos($tableColumn['data_default'], 'NULL') !== null) { $tableColumn['data_default'] = null; } $precision = null; $scale = null; $type = $this->_platform->getDoctrineTypeMapping($dbType); switch ($dbType) { case 'number': // @todo this sucks for the mapping stuff, is this deterministic maybe? if($tableColumn['data_scale'] > 0) { $precision = $tableColumn['data_precision']; $scale = $tableColumn['data_scale']; if ($precision == 0 && $scale == 1) { $type = 'boolean'; } else { $type = 'decimal'; } } $length = null; break; case 'pls_integer': case 'binary_integer': $length = null; break; case 'varchar': case 'varchar2': case 'nvarchar2': $fixed = false; break; case 'char': case 'nchar': $fixed = true; break; case 'date': case 'timestamp': $length = null; break; case 'float': $precision = $tableColumn['data_precision']; $scale = $tableColumn['data_scale']; $length = null; break; case 'clob': case 'nclob': $length = null; break; case 'blob': case 'raw': case 'long raw': case 'bfile': $length = null; break; case 'rowid': case 'urowid': default: $length = null; } $options = array( 'notnull' => (bool) ($tableColumn['nullable'] === 'N'), 'fixed' => (bool) $fixed, 'unsigned' => (bool) $unsigned, 'default' => $tableColumn['data_default'], 'length' => $length, 'precision' => $precision, 'scale' => $scale, 'platformDetails' => array(), ); return new Column($tableColumn['column_name'], \Doctrine\DBAL\Types\Type::getType($type), $options); } protected function _getPortableTableForeignKeysList($tableForeignKeys) { $list = array(); foreach ($tableForeignKeys as $key => $value) { $value = \array_change_key_case($value, CASE_LOWER); if (!isset($list[$value['constraint_name']])) { if ($value['delete_rule'] == "NO ACTION") { $value['delete_rule'] = null; } $list[$value['constraint_name']] = array( 'name' => $value['constraint_name'], 'local' => array(), 'foreign' => array(), 'foreignTable' => $value['references_table'], 'onDelete' => $value['delete_rule'], ); } $list[$value['constraint_name']]['local'][$value['position']] = $value['local_column']; $list[$value['constraint_name']]['foreign'][$value['position']] = $value['foreign_column']; } $result = array(); foreach($list AS $constraint) { $result[] = new ForeignKeyConstraint( array_values($constraint['local']), $constraint['foreignTable'], array_values($constraint['foreign']), $constraint['name'], array('onDelete' => $constraint['onDelete']) ); } return $result; } protected function _getPortableSequenceDefinition($sequence) { $sequence = \array_change_key_case($sequence, CASE_LOWER); return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['min_value']); } protected function _getPortableFunctionDefinition($function) { $function = \array_change_key_case($function, CASE_LOWER); return $function['name']; } protected function _getPortableDatabaseDefinition($database) { $database = \array_change_key_case($database, CASE_LOWER); return $database['username']; } public function createDatabase($database = null) { if (is_null($database)) { $database = $this->_conn->getDatabase(); } $params = $this->_conn->getParams(); $username = $database; $password = $params['password']; $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password; $result = $this->_conn->executeUpdate($query); $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO ' . $username; $result = $this->_conn->executeUpdate($query); return true; } public function dropAutoincrement($table) { $sql = $this->_platform->getDropAutoincrementSql($table); foreach ($sql as $query) { $this->_conn->executeUpdate($query); } return true; } public function dropTable($name) { $this->dropAutoincrement($name); return parent::dropTable($name); } }. */ namespace Doctrine\DBAL\Schema; use Doctrine\DBAL\Schema\Visitor\Visitor; /** * Sequence Structure * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class Sequence extends AbstractAsset { /** * @var int */ protected $_allocationSize = 1; /** * @var int */ protected $_initialValue = 1; /** * * @param string $name * @param int $allocationSize * @param int $initialValue */ public function __construct($name, $allocationSize=1, $initialValue=1) { $this->_setName($name); $this->_allocationSize = (is_numeric($allocationSize))?$allocationSize:1; $this->_initialValue = (is_numeric($initialValue))?$initialValue:1; } public function getAllocationSize() { return $this->_allocationSize; } public function getInitialValue() { return $this->_initialValue; } /** * @param Visitor $visitor */ public function visit(Visitor $visitor) { $visitor->acceptSequence($this); } } . */ namespace Doctrine\DBAL\Schema; /** * Represent the change of a column * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei */ class ColumnDiff { public $oldColumnName; /** * @var Column */ public $column; /** * @var array */ public $changedProperties = array(); public function __construct($oldColumnName, Column $column, array $changedProperties = array()) { $this->oldColumnName = $oldColumnName; $this->column = $column; $this->changedProperties = $changedProperties; } public function hasChanged($propertyName) { return in_array($propertyName, $this->changedProperties); } }getName()." requires a named foreign key, ". "but the given foreign key from (".implode(", ", $foreignKey->getColumns()).") onto foreign table ". "'".$foreignKey->getForeignTableName()."' (".implode(", ", $foreignKey->getForeignColumns()).") is currently ". "unnamed." ); } static public function alterTableChangeNotSupported($changeName) { return new self ("Alter table change not supported, given '$changeName'"); } }. */ namespace Doctrine\DBAL; /** * Contains all DBAL LockModes * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei * @author Roman Borschel */ class LockMode { const NONE = 0; const OPTIMISTIC = 1; const PESSIMISTIC_READ = 2; const PESSIMISTIC_WRITE = 4; final private function __construct() { } }. */ namespace Doctrine\DBAL; /** * Driver interface. * Interface that all DBAL drivers must implement. * * @since 2.0 */ interface Driver { /** * Attempts to create a connection with the database. * * @param array $params All connection parameters passed by the user. * @param string $username The username to use when connecting. * @param string $password The password to use when connecting. * @param array $driverOptions The driver options to use when connecting. * @return Doctrine\DBAL\Driver\Connection The database connection. */ public function connect(array $params, $username = null, $password = null, array $driverOptions = array()); /** * Gets the DatabasePlatform instance that provides all the metadata about * the platform this driver connects to. * * @return Doctrine\DBAL\Platforms\AbstractPlatform The database platform. */ public function getDatabasePlatform(); /** * Gets the SchemaManager that can be used to inspect and change the underlying * database schema of the platform this driver connects to. * * @param Doctrine\DBAL\Connection $conn * @return Doctrine\DBAL\SchemaManager */ public function getSchemaManager(Connection $conn); /** * Gets the name of the driver. * * @return string The name of the driver. */ public function getName(); /** * Get the name of the database connected to for this driver. * * @param Doctrine\DBAL\Connection $conn * @return string $database */ public function getDatabase(Connection $conn); }. */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\TableDiff, Doctrine\DBAL\Schema\Table; /** * PostgreSqlPlatform. * * @since 2.0 * @author Roman Borschel * @author Lukas Smith (PEAR MDB2 library) * @author Benjamin Eberlei * @todo Rename: PostgreSQLPlatform */ class PostgreSqlPlatform extends AbstractPlatform { /** * Returns part of a string. * * Note: Not SQL92, but common functionality. * * @param string $value the target $value the string or the string column. * @param int $from extract from this characeter. * @param int $len extract this amount of characters. * @return string sql that extracts part of a string. * @override */ public function getSubstringExpression($value, $from, $len = null) { if ($len === null) { return 'SUBSTR(' . $value . ', ' . $from . ')'; } else { return 'SUBSTR(' . $value . ', ' . $from . ', ' . $len . ')'; } } /** * Returns the SQL string to return the current system date and time. * * @return string */ public function getNowExpression() { return 'LOCALTIMESTAMP(0)'; } /** * regexp * * @return string the regular expression operator * @override */ public function getRegexpExpression() { return 'SIMILAR TO'; } /** * returns the position of the first occurrence of substring $substr in string $str * * @param string $substr literal string to find * @param string $str literal string * @param int $pos position to start at, beginning of string by default * @return integer */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos !== false) { $str = $this->getSubstringExpression($str, $startPos); return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END'; } else { return 'POSITION('.$substr.' IN '.$str.')'; } } /** * parses a literal boolean value and returns * proper sql equivalent * * @param string $value boolean value to be parsed * @return string parsed boolean value */ /*public function parseBoolean($value) { return $value; }*/ /** * Whether the platform supports sequences. * Postgres has native support for sequences. * * @return boolean */ public function supportsSequences() { return true; } /** * Whether the platform supports database schemas. * * @return boolean */ public function supportsSchemas() { return true; } /** * Whether the platform supports identity columns. * Postgres supports these through the SERIAL keyword. * * @return boolean */ public function supportsIdentityColumns() { return true; } /** * Whether the platform prefers sequences for ID generation. * * @return boolean */ public function prefersSequences() { return true; } public function getListDatabasesSQL() { return 'SELECT datname FROM pg_database'; } public function getListSequencesSQL($database) { return "SELECT relname FROM pg_class WHERE relkind = 'S' AND relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; } public function getListTablesSQL() { return "SELECT c.relname AS table_name FROM pg_class c, pg_user u WHERE c.relowner = u.usesysid AND c.relkind = 'r' AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) AND c.relname !~ '^(pg_|sql_)' UNION SELECT c.relname AS table_name FROM pg_class c WHERE c.relkind = 'r' AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) AND c.relname !~ '^pg_'"; } public function getListViewsSQL($database) { return 'SELECT viewname, definition FROM pg_views'; } public function getListTableForeignKeysSQL($table, $database = null) { return "SELECT r.conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = ( SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = '" . $table . "' AND pg_catalog.pg_table_is_visible(c.oid) ) AND r.contype = 'f'"; } public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } public function getDropViewSQL($name) { return 'DROP VIEW '. $name; } public function getListTableConstraintsSQL($table) { return "SELECT relname FROM pg_class WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname = '$table' AND pg_class.oid = pg_index.indrelid AND (indisunique = 't' OR indisprimary = 't') )"; } /** * @license New BSD License * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html * @param string $table * @return string */ public function getListTableIndexesSQL($table) { return "SELECT relname, pg_index.indisunique, pg_index.indisprimary, pg_index.indkey, pg_index.indrelid FROM pg_class, pg_index WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname='$table' AND pg_class.oid=pg_index.indrelid ) AND pg_index.indexrelid = oid"; } public function getListTableColumnsSQL($table) { return "SELECT a.attnum, a.attname AS field, t.typname AS type, format_type(a.atttypid, a.atttypmod) AS complete_type, (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.typname = format_type(a.atttypid, a.atttypmod)) AS domain_complete_type, a.attnotnull AS isnotnull, (SELECT 't' FROM pg_index WHERE c.oid = pg_index.indrelid AND pg_index.indkey[0] = a.attnum AND pg_index.indisprimary = 't' ) AS pri, (SELECT pg_attrdef.adsrc FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum ) AS default FROM pg_attribute a, pg_class c, pg_type t WHERE c.relname = '$table' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum"; } /** * create a new database * * @param string $name name of the database that should be created * @throws PDOException * @return void * @override */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * drop an existing database * * @param string $name name of the database that should be dropped * @throws PDOException * @access public */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * Return the FOREIGN KEY query section dealing with non-standard options * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... * * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey foreign key definition * @return string * @override */ public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { $query .= ' DEFERRABLE'; } else { $query .= ' NOT DEFERRABLE'; } if ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) { $query .= ' INITIALLY DEFERRED'; } else { $query .= ' INITIALLY IMMEDIATE'; } return $query; } /** * generates the sql for altering an existing table on postgresql * * @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() * @return array * @override */ public function getAlterTableSQL(TableDiff $diff) { $sql = array(); foreach ($diff->addedColumns as $column) { $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; } foreach ($diff->removedColumns as $column) { $query = 'DROP ' . $column->getName(); $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; } foreach ($diff->changedColumns AS $columnDiff) { $oldColumnName = $columnDiff->oldColumnName; $column = $columnDiff->column; if ($columnDiff->hasChanged('type')) { $type = $column->getType(); // here was a server version check before, but DBAL API does not support this anymore. $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSqlDeclaration($column->toArray(), $this); $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; } if ($columnDiff->hasChanged('default')) { $query = 'ALTER ' . $oldColumnName . ' SET ' . $this->getDefaultValueDeclarationSQL($column->toArray()); $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; } if ($columnDiff->hasChanged('notnull')) { $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotNull() ? 'SET' : 'DROP') . ' NOT NULL'; $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; } if ($columnDiff->hasChanged('autoincrement')) { if ($column->getAutoincrement()) { // add autoincrement $seqName = $diff->name . '_' . $oldColumnName . '_seq'; $sql[] = "CREATE SEQUENCE " . $seqName; $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->name . "))"; $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; $sql[] = "ALTER TABLE " . $diff->name . " " . $query; } else { // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT"; $sql[] = "ALTER TABLE " . $diff->name . " " . $query; } } } foreach ($diff->renamedColumns as $oldColumnName => $column) { $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME COLUMN ' . $oldColumnName . ' TO ' . $column->getName(); } if ($diff->newName !== false) { $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME TO ' . $diff->newName; } $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); return $sql; } /** * Gets the SQL to create a sequence on this platform. * * @param \Doctrine\DBAL\Schema\Sequence $sequence * @return string */ public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getName() . ' INCREMENT BY ' . $sequence->getAllocationSize() . ' MINVALUE ' . $sequence->getInitialValue() . ' START ' . $sequence->getInitialValue(); } /** * Drop existing sequence * @param \Doctrine\DBAL\Schema\Sequence $sequence * @return string */ public function getDropSequenceSQL($sequence) { if ($sequence instanceof \Doctrine\DBAL\Schema\Sequence) { $sequence = $sequence->getName(); } return 'DROP SEQUENCE ' . $sequence; } /** * @param ForeignKeyConstraint|string $foreignKey * @param Table|string $table * @return string */ public function getDropForeignKeySQL($foreignKey, $table) { return $this->getDropConstraintSQL($foreignKey, $table); } /** * Gets the SQL used to create a table. * * @param unknown_type $tableName * @param array $columns * @param array $options * @return unknown */ protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')'; $sql[] = $query; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] AS $index) { $sql[] = $this->getCreateIndexSQL($index, $tableName); } } if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); } } return $sql; } /** * Postgres wants boolean values converted to the strings 'true'/'false'. * * @param array $item * @override */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $key => $value) { if (is_bool($value) || is_numeric($item)) { $item[$key] = ($value) ? 'true' : 'false'; } } } else { if (is_bool($item) || is_numeric($item)) { $item = ($item) ? 'true' : 'false'; } } return $item; } public function getSequenceNextValSQL($sequenceName) { return "SELECT NEXTVAL('" . $sequenceName . "')"; } public function getSetTransactionIsolationSQL($level) { return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * @override */ public function getBooleanTypeDeclarationSQL(array $field) { return 'BOOLEAN'; } /** * @override */ public function getIntegerTypeDeclarationSQL(array $field) { if ( ! empty($field['autoincrement'])) { return 'SERIAL'; } return 'INT'; } /** * @override */ public function getBigIntTypeDeclarationSQL(array $field) { if ( ! empty($field['autoincrement'])) { return 'BIGSERIAL'; } return 'BIGINT'; } /** * @override */ public function getSmallIntTypeDeclarationSQL(array $field) { return 'SMALLINT'; } /** * @override */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIMESTAMP(0) WITHOUT TIME ZONE'; } /** * @override */ public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * @override */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * @override */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIME(0) WITHOUT TIME ZONE'; } /** * @override */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { return ''; } /** * Gets the SQL snippet used to declare a VARCHAR column on the MySql platform. * * @params array $field * @override */ public function getVarcharTypeDeclarationSQL(array $field) { if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->getVarcharMaxLength(); } else { $field['length'] = false; } } $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); } /** @override */ public function getClobTypeDeclarationSQL(array $field) { return 'TEXT'; } /** * Get the platform name for this instance * * @return string */ public function getName() { return 'postgresql'; } /** * Gets the character casing of a column in an SQL result set. * * PostgreSQL returns all column names in SQL result sets in lowercase. * * @param string $column The column name for which to get the correct character casing. * @return string The column name in the character casing used in SQL result sets. */ public function getSQLResultCasing($column) { return strtolower($column); } public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sO'; } /** * Get the insert sql for an empty insert statement * * @param string $tableName * @param string $identifierColumnName * @return string $sql */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; } /** * @inheritdoc */ public function getTruncateTableSQL($tableName, $cascade = false) { return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':''; } public function getReadLockSQL() { return 'FOR SHARE'; } protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = array( 'smallint' => 'smallint', 'int2' => 'smallint', 'serial' => 'integer', 'serial4' => 'integer', 'int' => 'integer', 'int4' => 'integer', 'integer' => 'integer', 'bigserial' => 'bigint', 'serial8' => 'bigint', 'bigint' => 'bigint', 'int8' => 'bigint', 'bool' => 'boolean', 'boolean' => 'boolean', 'text' => 'text', 'varchar' => 'string', 'interval' => 'string', '_varchar' => 'string', 'char' => 'string', 'bpchar' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'timestamp' => 'datetime', 'timestamptz' => 'datetimetz', 'time' => 'time', 'timetz' => 'time', 'float' => 'float', 'float4' => 'float', 'float8' => 'float', 'double' => 'float', 'double precision' => 'float', 'real' => 'float', 'decimal' => 'decimal', 'money' => 'decimal', 'numeric' => 'decimal', 'year' => 'date', ); } } . */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Schema\Index, Doctrine\DBAL\Schema\Table; /** * The MsSqlPlatform provides the behavior, features and SQL dialect of the * MySQL database platform. * * @since 2.0 * @author Roman Borschel * @author Jonathan H. Wage * @author Benjamin Eberlei * @todo Rename: MsSQLPlatform */ class MsSqlPlatform extends AbstractPlatform { /** * Whether the platform prefers identity columns for ID generation. * MsSql prefers "autoincrement" identity columns since sequences can only * be emulated with a table. * * @return boolean * @override */ public function prefersIdentityColumns() { return true; } /** * Whether the platform supports identity columns. * MsSql supports this through AUTO_INCREMENT columns. * * @return boolean * @override */ public function supportsIdentityColumns() { return true; } /** * Whether the platform supports releasing savepoints. * * @return boolean */ public function supportsReleaseSavepoints() { return false; } /** * create a new database * * @param string $name name of the database that should be created * @return string * @override */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * drop an existing database * * @param string $name name of the database that should be dropped * @return string * @override */ public function getDropDatabaseSQL($name) { // @todo do we really need to force drop? return 'ALTER DATABASE [' . $name . '] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE ' . $name . ';'; } /** * @override */ public function quoteIdentifier($str) { return '[' . $str . ']'; } /** * @override */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) { $foreignKey = $foreignKey->getName(); } if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } /** * @override */ public function getDropIndexSQL($index, $table=null) { if ($index instanceof \Doctrine\DBAL\Schema\Index) { $index_ = $index; $index = $index->getName(); } else if (!is_string($index)) { throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); } if (!isset($table)) { return 'DROP INDEX ' . $index; } else { if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index') ALTER TABLE " . $this->quoteIdentifier($table) . " DROP CONSTRAINT " . $this->quoteIdentifier($index) . " ELSE DROP INDEX " . $this->quoteIdentifier($index) . " ON " . $this->quoteIdentifier($table); } } /** * @override */ public function getCreateTableSQL(Table $table, $createFlags=self::CREATE_INDEXES) { $sql = parent::getCreateTableSQL($table, $createFlags); $primary = array(); foreach ($table->getIndexes() AS $index) { /* @var $index Index */ if ($index->isPrimary()) { $primary = $index->getColumns(); } } if (count($primary) === 1) { foreach ($table->getForeignKeys() AS $definition) { $columns = $definition->getLocalColumns(); if (count($columns) === 1 && in_array($columns[0], $primary)) { $sql[0] = str_replace(' IDENTITY', '', $sql[0]); } } } return $sql; } /** * @override */ protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) { $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $name => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); } } if (isset($options['primary']) && !empty($options['primary'])) { $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if (!empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql[] = $query; if (isset($options['indexes']) && !empty($options['indexes'])) { foreach ($options['indexes'] AS $index) { $sql[] = $this->getCreateIndexSQL($index, $tableName); } } if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] AS $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); } } return $sql; } /** * @override */ public function getAlterTableSQL(TableDiff $diff) { $queryParts = array(); if ($diff->newName !== false) { $queryParts[] = 'RENAME TO ' . $diff->newName; } foreach ($diff->addedColumns AS $fieldName => $column) { $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } foreach ($diff->removedColumns AS $column) { $queryParts[] = 'DROP COLUMN ' . $column->getName(); } foreach ($diff->changedColumns AS $columnDiff) { /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */ $column = $columnDiff->column; $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } foreach ($diff->renamedColumns AS $oldColumnName => $column) { $queryParts[] = 'CHANGE ' . $oldColumnName . ' ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } $sql = array(); foreach ($queryParts as $query) { $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; } $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); return $sql; } /** * @override */ public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; } /** * @override */ public function getShowDatabasesSQL() { return 'SHOW DATABASES'; } /** * @override */ public function getListTablesSQL() { return "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name"; } /** * @override */ public function getListTableColumnsSQL($table) { return 'exec sp_columns @table_name = ' . $table; } /** * @override */ public function getListTableForeignKeysSQL($table, $database = null) { return "SELECT f.name AS ForeignKey, SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, OBJECT_NAME (f.parent_object_id) AS TableName, COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, f.delete_referential_action_desc, f.update_referential_action_desc FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id ON f.OBJECT_ID = fc.constraint_object_id WHERE OBJECT_NAME (f.parent_object_id) = '" . $table . "'"; } /** * @override */ public function getListTableIndexesSQL($table) { return "exec sp_helpindex '" . $table . "'"; } /** * @override */ public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } /** * @override */ public function getListViewsSQL($database) { return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name"; } /** * @override */ public function getDropViewSQL($name) { return 'DROP VIEW ' . $name; } /** * Returns the regular expression operator. * * @return string * @override */ public function getRegexpExpression() { return 'RLIKE'; } /** * Returns global unique identifier * * @return string to get global unique identifier * @override */ public function getGuidExpression() { return 'UUID()'; } /** * @override */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos == false) { return 'CHARINDEX(' . $substr . ', ' . $str . ')'; } else { return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; } } /** * @override */ public function getModExpression($expression1, $expression2) { return $expression1 . ' % ' . $expression2; } /** * @override */ public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) { // @todo $trimFn = ''; $trimChar = ($char != false) ? (', ' . $char) : ''; if ($pos == self::TRIM_LEADING) { $trimFn = 'LTRIM'; } else if ($pos == self::TRIM_TRAILING) { $trimFn = 'RTRIM'; } else { return 'LTRIM(RTRIM(' . $str . '))'; } return $trimFn . '(' . $str . ')'; } /** * @override */ public function getConcatExpression() { $args = func_get_args(); return '(' . implode(' + ', $args) . ')'; } public function getListDatabasesSQL() { return 'SELECT * FROM SYS.DATABASES'; } /** * @override */ public function getSubstringExpression($value, $from, $len = null) { if (!is_null($len)) { return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $len . ')'; } return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)'; } /** * @override */ public function getLengthExpression($column) { return 'LEN(' . $column . ')'; } /** * @override */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * @override */ public function getIntegerTypeDeclarationSQL(array $field) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getBigIntTypeDeclarationSQL(array $field) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getSmallIntTypeDeclarationSQL(array $field) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); } /** @override */ public function getVarcharTypeDeclarationSQL(array $field) { if (!isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->getVarcharMaxLength(); } else { $field['length'] = false; } } $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NTEXT'); } /** @override */ public function getClobTypeDeclarationSQL(array $field) { return 'TEXT'; } /** * @override */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { $autoinc = ''; if (!empty($columnDef['autoincrement'])) { $autoinc = ' IDENTITY'; } $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; return $unsigned . $autoinc; } /** * @override */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { // 6 - microseconds precision length return 'DATETIME2(6)'; } /** * @override */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * @override */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIME(0)'; } /** * @override */ public function getBooleanTypeDeclarationSQL(array $field) { return 'BIT'; } /** * Adds an adapter-specific LIMIT clause to the SELECT statement. * * @param string $query * @param mixed $limit * @param mixed $offset * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html * @return string */ public function modifyLimitQuery($query, $limit, $offset = null) { if ($limit > 0) { $count = intval($limit); $offset = intval($offset); if ($offset < 0) { throw new Doctrine_Connection_Exception("LIMIT argument offset=$offset is not valid"); } if ($offset == 0) { $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $query); } else { $orderby = stristr($query, 'ORDER BY'); if (!$orderby) { $over = 'ORDER BY (SELECT 0)'; } else { $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby); } // Remove ORDER BY clause from $query $query = preg_replace('/\s+ORDER BY(.*)/', '', $query); // Add ORDER BY clause as an argument for ROW_NUMBER() $query = "SELECT ROW_NUMBER() OVER ($over) AS \"doctrine_rownum\", * FROM ($query) AS inner_tbl"; $start = $offset + 1; $end = $offset + $count; $query = "WITH outer_tbl AS ($query) SELECT * FROM outer_tbl WHERE \"doctrine_rownum\" BETWEEN $start AND $end"; } } return $query; } /** * @override */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $key => $value) { if (is_bool($value) || is_numeric($item)) { $item[$key] = ($value) ? 'TRUE' : 'FALSE'; } } } else { if (is_bool($item) || is_numeric($item)) { $item = ($item) ? 'TRUE' : 'FALSE'; } } return $item; } /** * @override */ public function getCreateTemporaryTableSnippetSQL() { return "CREATE TABLE"; } /** * @override */ public function getTemporaryTableName($tableName) { return '#' . $tableName; } /** * @override */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s.u'; } /** * @override */ public function getDateTimeTzFormatString() { return $this->getDateTimeFormatString(); } /** * Get the platform name for this instance * * @return string */ public function getName() { return 'mssql'; } protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = array( 'bigint' => 'bigint', 'numeric' => 'decimal', 'bit' => 'boolean', 'smallint' => 'smallint', 'decimal' => 'decimal', 'smallmoney' => 'integer', 'int' => 'integer', 'tinyint' => 'smallint', 'money' => 'integer', 'float' => 'float', 'real' => 'float', 'double' => 'float', 'double precision' => 'float', 'date' => 'date', 'datetimeoffset' => 'datetimetz', 'datetime2' => 'datetime', 'smalldatetime' => 'datetime', 'datetime' => 'datetime', 'time' => 'time', 'char' => 'string', 'varchar' => 'string', 'text' => 'text', 'nchar' => 'string', 'nvarchar' => 'string', 'ntext' => 'text', 'binary' => 'text', 'varbinary' => 'text', 'image' => 'text', ); } /** * Generate SQL to create a new savepoint * * @param string $savepoint * @return string */ public function createSavePoint($savepoint) { return 'SAVE TRANSACTION ' . $savepoint; } /** * Generate SQL to release a savepoint * * @param string $savepoint * @return string */ public function releaseSavePoint($savepoint) { return ''; } /** * Generate SQL to rollback a savepoint * * @param string $savepoint * @return string */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TRANSACTION ' . $savepoint; } } . */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\Schema\TableDiff; /** * OraclePlatform. * * @since 2.0 * @author Roman Borschel * @author Lukas Smith (PEAR MDB2 library) * @author Benjamin Eberlei */ class OraclePlatform extends AbstractPlatform { /** * return string to call a function to get a substring inside an SQL statement * * Note: Not SQL92, but common functionality. * * @param string $value an sql string literal or column name/alias * @param integer $position where to start the substring portion * @param integer $length the substring portion length * @return string SQL substring function with given parameters * @override */ public function getSubstringExpression($value, $position, $length = null) { if ($length !== null) { return "SUBSTR($value, $position, $length)"; } return "SUBSTR($value, $position)"; } /** * Return string to call a variable with the current timestamp inside an SQL statement * There are three special variables for current date and time: * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) * - CURRENT_DATE (date, DATE type) * - CURRENT_TIME (time, TIME type) * * @return string to call a variable with the current timestamp * @override */ public function getNowExpression($type = 'timestamp') { switch ($type) { case 'date': case 'time': case 'timestamp': default: return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; } } /** * returns the position of the first occurrence of substring $substr in string $str * * @param string $substr literal string to find * @param string $str literal string * @param int $pos position to start at, beginning of string by default * @return integer */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos == false) { return 'INSTR('.$str.', '.$substr.')'; } else { return 'INSTR('.$str.', '.$substr.', '.$startPos.')'; } } /** * Returns global unique identifier * * @return string to get global unique identifier * @override */ public function getGuidExpression() { return 'SYS_GUID()'; } /** * Gets the SQL used to create a sequence that starts with a given value * and increments by the given allocation size. * * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection * in {@see listSequences()} * * @param \Doctrine\DBAL\Schema\Sequence $sequence * @return string */ public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence) { return 'CREATE SEQUENCE ' . $sequence->getName() . ' START WITH ' . $sequence->getInitialValue() . ' MINVALUE ' . $sequence->getInitialValue() . ' INCREMENT BY ' . $sequence->getAllocationSize(); } /** * {@inheritdoc} * * @param string $sequenceName * @override */ public function getSequenceNextValSQL($sequenceName) { return 'SELECT ' . $sequenceName . '.nextval FROM DUAL'; } /** * {@inheritdoc} * * @param integer $level * @override */ public function getSetTransactionIsolationSQL($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: return 'READ COMMITTED'; case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: return 'SERIALIZABLE'; default: return parent::_getTransactionIsolationLevelSQL($level); } } /** * @override */ public function getBooleanTypeDeclarationSQL(array $field) { return 'NUMBER(1)'; } /** * @override */ public function getIntegerTypeDeclarationSQL(array $field) { return 'NUMBER(10)'; } /** * @override */ public function getBigIntTypeDeclarationSQL(array $field) { return 'NUMBER(20)'; } /** * @override */ public function getSmallIntTypeDeclarationSQL(array $field) { return 'NUMBER(5)'; } /** * @override */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIMESTAMP(0)'; } /** * @override */ public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) { return 'TIMESTAMP(0) WITH TIME ZONE'; } /** * @override */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * @override */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * @override */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { return ''; } /** * Gets the SQL snippet used to declare a VARCHAR column on the Oracle platform. * * @params array $field * @override */ public function getVarcharTypeDeclarationSQL(array $field) { if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->getVarcharMaxLength(); } else { $field['length'] = false; } } $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)') : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); } /** @override */ public function getClobTypeDeclarationSQL(array $field) { return 'CLOB'; } public function getListDatabasesSQL() { return 'SELECT username FROM all_users'; } public function getListSequencesSQL($database) { return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ". "WHERE SEQUENCE_OWNER = '".strtoupper($database)."'"; } /** * * @param string $table * @param array $columns * @param array $options * @return array */ protected function _getCreateTableSQL($table, array $columns, array $options = array()) { $indexes = isset($options['indexes']) ? $options['indexes'] : array(); $options['indexes'] = array(); $sql = parent::_getCreateTableSQL($table, $columns, $options); foreach ($columns as $name => $column) { if (isset($column['sequence'])) { $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1); } if (isset($column['autoincrement']) && $column['autoincrement'] || (isset($column['autoinc']) && $column['autoinc'])) { $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table)); } } if (isset($indexes) && ! empty($indexes)) { foreach ($indexes as $indexName => $index) { $sql[] = $this->getCreateIndexSQL($index, $table); } } return $sql; } /** * @license New BSD License * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html * @param string $table * @return string */ public function getListTableIndexesSQL($table) { $table = strtoupper($table); return "SELECT uind.index_name AS name, " . " uind.index_type AS type, " . " decode( uind.uniqueness, 'NONUNIQUE', 0, 'UNIQUE', 1 ) AS is_unique, " . " uind_col.column_name AS column_name, " . " uind_col.column_position AS column_pos, " . " (SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.constraint_name = uind.index_name) AS is_primary ". "FROM user_indexes uind, user_ind_columns uind_col " . "WHERE uind.index_name = uind_col.index_name AND uind_col.table_name = '$table' ORDER BY uind_col.column_position ASC"; } public function getListTablesSQL() { return 'SELECT * FROM sys.user_tables'; } public function getListViewsSQL($database) { return 'SELECT view_name, text FROM sys.user_views'; } public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } public function getDropViewSQL($name) { return 'DROP VIEW '. $name; } public function getCreateAutoincrementSql($name, $table, $start = 1) { $table = strtoupper($table); $sql = array(); $indexName = $table . '_AI_PK'; $definition = array( 'primary' => true, 'columns' => array($name => true), ); $idx = new \Doctrine\DBAL\Schema\Index($indexName, array($name), true, true); $sql[] = 'DECLARE constraints_Count NUMBER; BEGIN SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \''.$table.'\' AND CONSTRAINT_TYPE = \'P\'; IF constraints_Count = 0 OR constraints_Count = \'\' THEN EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $table).'\'; END IF; END;'; $sequenceName = $table . '_SEQ'; $sequence = new \Doctrine\DBAL\Schema\Sequence($sequenceName, $start); $sql[] = $this->getCreateSequenceSQL($sequence); $triggerName = $table . '_AI_PK'; $sql[] = 'CREATE TRIGGER ' . $triggerName . ' BEFORE INSERT ON ' . $table . ' FOR EACH ROW DECLARE last_Sequence NUMBER; last_InsertID NUMBER; BEGIN SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL; IF (:NEW.' . $name . ' IS NULL OR :NEW.'.$name.' = 0) THEN SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL; ELSE SELECT NVL(Last_Number, 0) INTO last_Sequence FROM User_Sequences WHERE Sequence_Name = \'' . $sequenceName . '\'; SELECT :NEW.' . $name . ' INTO last_InsertID FROM DUAL; WHILE (last_InsertID > last_Sequence) LOOP SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; END LOOP; END IF; END;'; return $sql; } public function getDropAutoincrementSql($table) { $table = strtoupper($table); $trigger = $table . '_AI_PK'; if ($trigger) { $sql[] = 'DROP TRIGGER ' . $trigger; $sql[] = $this->getDropSequenceSQL($table.'_SEQ'); $indexName = $table . '_AI_PK'; $sql[] = $this->getDropConstraintSQL($indexName, $table); } return $sql; } public function getListTableForeignKeysSQL($table) { $table = strtoupper($table); return "SELECT alc.constraint_name, alc.DELETE_RULE, alc.search_condition, cols.column_name \"local_column\", cols.position, r_alc.table_name \"references_table\", r_cols.column_name \"foreign_column\" FROM all_cons_columns cols LEFT JOIN all_constraints alc ON alc.constraint_name = cols.constraint_name AND alc.owner = cols.owner LEFT JOIN all_constraints r_alc ON alc.r_constraint_name = r_alc.constraint_name AND alc.r_owner = r_alc.owner LEFT JOIN all_cons_columns r_cols ON r_alc.constraint_name = r_cols.constraint_name AND r_alc.owner = r_cols.owner AND cols.position = r_cols.position WHERE alc.constraint_name = cols.constraint_name AND alc.constraint_type = 'R' AND alc.table_name = '".$table."'"; } public function getListTableConstraintsSQL($table) { $table = strtoupper($table); return 'SELECT * FROM user_constraints WHERE table_name = \'' . $table . '\''; } public function getListTableColumnsSQL($table) { $table = strtoupper($table); return "SELECT * FROM all_tab_columns WHERE table_name = '" . $table . "' ORDER BY column_name"; } /** * * @param \Doctrine\DBAL\Schema\Sequence $sequence * @return string */ public function getDropSequenceSQL($sequence) { if ($sequence instanceof \Doctrine\DBAL\Schema\Sequence) { $sequence = $sequence->getName(); } return 'DROP SEQUENCE ' . $sequence; } /** * @param ForeignKeyConstraint|string $foreignKey * @param Table|string $table * @return string */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) { $foreignKey = $foreignKey->getName(); } if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; } public function getDropDatabaseSQL($database) { return 'DROP USER ' . $database . ' CASCADE'; } /** * Gets the sql statements for altering an existing table. * * The method returns an array of sql statements, since some platforms need several statements. * * @param string $diff->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. * @return array */ public function getAlterTableSQL(TableDiff $diff) { $sql = array(); $fields = array(); foreach ($diff->addedColumns AS $column) { $fields[] = $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } if (count($fields)) { $sql[] = 'ALTER TABLE ' . $diff->name . ' ADD (' . implode(', ', $fields) . ')'; } $fields = array(); foreach ($diff->changedColumns AS $columnDiff) { $column = $columnDiff->column; $fields[] = $column->getName(). ' ' . $this->getColumnDeclarationSQL('', $column->toArray()); } if (count($fields)) { $sql[] = 'ALTER TABLE ' . $diff->name . ' MODIFY (' . implode(', ', $fields) . ')'; } foreach ($diff->renamedColumns AS $oldColumnName => $column) { $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME COLUMN ' . $oldColumnName .' TO ' . $column->getName(); } $fields = array(); foreach ($diff->removedColumns AS $column) { $fields[] = $column->getName(); } if (count($fields)) { $sql[] = 'ALTER TABLE ' . $diff->name . ' DROP COLUMN ' . implode(', ', $fields); } if ($diff->newName !== false) { $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME TO ' . $diff->newName; } $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); return $sql; } /** * Whether the platform prefers sequences for ID generation. * * @return boolean */ public function prefersSequences() { return true; } /** * Get the platform name for this instance * * @return string */ public function getName() { return 'oracle'; } /** * Adds an driver-specific LIMIT clause to the query * * @param string $query query to modify * @param integer $limit limit the number of rows * @param integer $offset start reading from given offset * @return string the modified query */ public function modifyLimitQuery($query, $limit, $offset = null) { $limit = (int) $limit; $offset = (int) $offset; if (preg_match('/^\s*SELECT/i', $query)) { if ( ! preg_match('/\sFROM\s/i', $query)) { $query .= " FROM dual"; } if ($limit > 0) { $max = $offset + $limit; $column = '*'; if ($offset > 0) { $min = $offset + 1; $query = 'SELECT b.'.$column.' FROM ('. 'SELECT a.*, ROWNUM AS doctrine_rownum FROM (' . $query . ') a '. ') b '. 'WHERE doctrine_rownum BETWEEN ' . $min . ' AND ' . $max; } else { $query = 'SELECT a.'.$column.' FROM (' . $query .') a WHERE ROWNUM <= ' . $max; } } } return $query; } /** * Gets the character casing of a column in an SQL result set of this platform. * * Oracle returns all column names in SQL result sets in uppercase. * * @param string $column The column name for which to get the correct character casing. * @return string The column name in the character casing used in SQL result sets. */ public function getSQLResultCasing($column) { return strtoupper($column); } public function getCreateTemporaryTableSnippetSQL() { return "CREATE GLOBAL TEMPORARY TABLE"; } public function getDateTimeTzFormatString() { return 'Y-m-d H:i:sP'; } public function getDateFormatString() { return 'Y-m-d 00:00:00'; } public function getTimeFormatString() { return '1900-01-01 H:i:s'; } public function fixSchemaElementName($schemaElementName) { if (strlen($schemaElementName) > 30) { // Trim it return substr($schemaElementName, 0, 30); } return $schemaElementName; } /** * Maximum length of any given databse identifier, like tables or column names. * * @return int */ public function getMaxIdentifierLength() { return 30; } /** * Whether the platform supports sequences. * * @return boolean */ public function supportsSequences() { return true; } public function supportsForeignKeyOnUpdate() { return false; } /** * Whether the platform supports releasing savepoints. * * @return boolean */ public function supportsReleaseSavepoints() { return false; } /** * @inheritdoc */ public function getTruncateTableSQL($tableName, $cascade = false) { return 'TRUNCATE TABLE '.$tableName; } /** * This is for test reasons, many vendors have special requirements for dummy statements. * * @return string */ public function getDummySelectSQL() { return 'SELECT 1 FROM DUAL'; } protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = array( 'integer' => 'integer', 'number' => 'integer', 'pls_integer' => 'boolean', 'binary_integer' => 'boolean', 'varchar' => 'string', 'varchar2' => 'string', 'nvarchar2' => 'string', 'char' => 'string', 'nchar' => 'string', 'date' => 'datetime', 'timestamp' => 'datetime', 'timestamptz' => 'datetimetz', 'float' => 'float', 'long' => 'string', 'clob' => 'text', 'nclob' => 'text', 'rowid' => 'string', 'urowid' => 'string' ); } /** * Generate SQL to release a savepoint * * @param string $savepoint * @return string */ public function releaseSavePoint($savepoint) { return ''; } } . */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\TableDiff; class DB2Platform extends AbstractPlatform { public function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = array( 'smallint' => 'smallint', 'bigint' => 'bigint', 'integer' => 'integer', 'time' => 'time', 'date' => 'date', 'varchar' => 'string', 'character' => 'string', 'clob' => 'text', 'decimal' => 'decimal', 'double' => 'decimal', 'real' => 'decimal', 'timestamp' => 'datetime', ); } /** * Gets the SQL snippet used to declare a VARCHAR column type. * * @param array $field */ public function getVarcharTypeDeclarationSQL(array $field) { if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->getVarcharMaxLength(); } else { $field['length'] = false; } } $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** * Gets the SQL snippet used to declare a CLOB column type. * * @param array $field */ public function getClobTypeDeclarationSQL(array $field) { // todo clob(n) with $field['length']; return 'CLOB(1M)'; } /** * Gets the name of the platform. * * @return string */ public function getName() { return 'db2'; } /** * Gets the SQL snippet that declares a boolean column. * * @param array $columnDef * @return string */ public function getBooleanTypeDeclarationSQL(array $columnDef) { return 'SMALLINT'; } /** * Gets the SQL snippet that declares a 4 byte integer column. * * @param array $columnDef * @return string */ public function getIntegerTypeDeclarationSQL(array $columnDef) { return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** * Gets the SQL snippet that declares an 8 byte integer column. * * @param array $columnDef * @return string */ public function getBigIntTypeDeclarationSQL(array $columnDef) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** * Gets the SQL snippet that declares a 2 byte integer column. * * @param array $columnDef * @return string */ public function getSmallIntTypeDeclarationSQL(array $columnDef) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); } /** * Gets the SQL snippet that declares common properties of an integer column. * * @param array $columnDef * @return string */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { $autoinc = ''; if ( ! empty($columnDef['autoincrement'])) { $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; } return $autoinc; } /** * Obtain DBMS specific SQL to be used to create datetime fields in * statements like CREATE TABLE * * @param array $fieldDeclaration * @return string */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { return "TIMESTAMP(0) WITH DEFAULT"; } return 'TIMESTAMP(0)'; } /** * Obtain DBMS specific SQL to be used to create date fields in statements * like CREATE TABLE. * * @param array $fieldDeclaration * @return string */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * Obtain DBMS specific SQL to be used to create time fields in statements * like CREATE TABLE. * * @param array $fieldDeclaration * @return string */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIME'; } public function getListDatabasesSQL() { throw DBALException::notSupported(__METHOD__); } public function getListSequencesSQL($database) { throw DBALException::notSupported(__METHOD__); } public function getListTableConstraintsSQL($table) { throw DBALException::notSupported(__METHOD__); } /** * This code fragment is originally from the Zend_Db_Adapter_Db2 class. * * @license New BSD License * @param string $table * @return string */ public function getListTableColumnsSQL($table) { return "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, c.typename, c.default, c.nulls, c.length, c.scale, c.identity, tc.type AS tabconsttype, k.colseq FROM syscat.columns c LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc ON (k.tabschema = tc.tabschema AND k.tabname = tc.tabname AND tc.type = 'P')) ON (c.tabschema = k.tabschema AND c.tabname = k.tabname AND c.colname = k.colname) WHERE UPPER(c.tabname) = UPPER('" . $table . "') ORDER BY c.colno"; } public function getListTablesSQL() { return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'"; } public function getListUsersSQL() { throw DBALException::notSupported(__METHOD__); } /** * Get the SQL to list all views of a database or user. * * @param string $database * @return string */ public function getListViewsSQL($database) { return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS"; } public function getListTableIndexesSQL($table) { return "SELECT NAME, COLNAMES, UNIQUERULE FROM SYSIBM.SYSINDEXES WHERE TBNAME = UPPER('" . $table . "')"; } public function getListTableForeignKeysSQL($table) { return "SELECT TBNAME, RELNAME, REFTBNAME, DELETERULE, UPDATERULE, FKCOLNAMES, PKCOLNAMES ". "FROM SYSIBM.SYSRELS WHERE TBNAME = UPPER('".$table."')"; } public function getCreateViewSQL($name, $sql) { return "CREATE VIEW ".$name." AS ".$sql; } public function getDropViewSQL($name) { return "DROP VIEW ".$name; } public function getDropSequenceSQL($sequence) { throw DBALException::notSupported(__METHOD__); } public function getSequenceNextValSQL($sequenceName) { throw DBALException::notSupported(__METHOD__); } public function getCreateDatabaseSQL($database) { return "CREATE DATABASE ".$database; } public function getDropDatabaseSQL($database) { return "DROP DATABASE ".$database.";"; } public function supportsCreateDropDatabase() { return false; } /** * Whether the platform supports releasing savepoints. * * @return boolean */ public function supportsReleaseSavepoints() { return false; } /** * Gets the SQL specific for the platform to get the current date. * * @return string */ public function getCurrentDateSQL() { return 'VALUES CURRENT DATE'; } /** * Gets the SQL specific for the platform to get the current time. * * @return string */ public function getCurrentTimeSQL() { return 'VALUES CURRENT TIME'; } /** * Gets the SQL specific for the platform to get the current timestamp * * @return string */ public function getCurrentTimestampSQL() { return "VALUES CURRENT TIMESTAMP"; } /** * Obtain DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @param string $name name of the index * @param Index $index index definition * @return string DBMS specific SQL code portion needed to set an index */ public function getIndexDeclarationSQL($name, Index $index) { return $this->getUniqueConstraintDeclarationSQL($name, $index); } /** * @param string $tableName * @param array $columns * @param array $options * @return array */ protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) { $indexes = array(); if (isset($options['indexes'])) { $indexes = $options['indexes']; } $options['indexes'] = array(); $sqls = parent::_getCreateTableSQL($tableName, $columns, $options); foreach ($indexes as $index => $definition) { $sqls[] = $this->getCreateIndexSQL($definition, $tableName); } return $sqls; } /** * Gets the SQL to alter an existing table. * * @param TableDiff $diff * @return array */ public function getAlterTableSQL(TableDiff $diff) { $sql = array(); $queryParts = array(); foreach ($diff->addedColumns AS $fieldName => $column) { $queryParts[] = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } foreach ($diff->removedColumns AS $column) { $queryParts[] = 'DROP COLUMN ' . $column->getName(); } foreach ($diff->changedColumns AS $columnDiff) { /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */ $column = $columnDiff->column; $queryParts[] = 'ALTER ' . ($columnDiff->oldColumnName) . ' ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } foreach ($diff->renamedColumns AS $oldColumnName => $column) { $queryParts[] = 'RENAME ' . $oldColumnName . ' TO ' . $column->getName(); } if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(" ", $queryParts); } $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); if ($diff->newName !== false) { $sql[] = 'RENAME TABLE TO ' . $diff->newName; } return $sql; } public function getDefaultValueDeclarationSQL($field) { if (isset($field['notnull']) && $field['notnull'] && !isset($field['default'])) { if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) { $field['default'] = 0; } else if((string)$field['type'] == "DateTime") { $field['default'] = "00-00-00 00:00:00"; } else if ((string)$field['type'] == "Date") { $field['default'] = "00-00-00"; } else if((string)$field['type'] == "Time") { $field['default'] = "00:00:00"; } else { $field['default'] = ''; } } unset($field['default']); // @todo this needs fixing if (isset($field['version']) && $field['version']) { if ((string)$field['type'] != "DateTime") { $field['default'] = "1"; } } return parent::getDefaultValueDeclarationSQL($field); } /** * Get the insert sql for an empty insert statement * * @param string $tableName * @param string $identifierColumnName * @return string $sql */ public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) { return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)'; } public function getCreateTemporaryTableSnippetSQL() { return "DECLARE GLOBAL TEMPORARY TABLE"; } /** * DB2 automatically moves temporary tables into the SESSION. schema. * * @param string $tableName * @return string */ public function getTemporaryTableName($tableName) { return "SESSION." . $tableName; } public function modifyLimitQuery($query, $limit, $offset = null) { if ($limit === null && $offset === null) { return $query; } $limit = (int)$limit; $offset = (int)(($offset)?:0); // Todo OVER() needs ORDER BY data! $sql = 'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* '. 'FROM (' . $query . ') db21) db22 WHERE db22.DC_ROWNUM BETWEEN ' . ($offset+1) .' AND ' . ($offset+$limit); return $sql; } /** * returns the position of the first occurrence of substring $substr in string $str * * @param string $substr literal string to find * @param string $str literal string * @param int $pos position to start at, beginning of string by default * @return integer */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos == false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } else { return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; } } /** * return string to call a function to get a substring inside an SQL statement * * Note: Not SQL92, but common functionality. * * SQLite only supports the 2 parameter variant of this function * * @param string $value an sql string literal or column name/alias * @param integer $from where to start the substring portion * @param integer $len the substring portion length * @return string */ public function getSubstringExpression($value, $from, $len = null) { if ($len === null) return 'SUBSTR(' . $value . ', ' . $from . ')'; else { return 'SUBSTR(' . $value . ', ' . $from . ', ' . $len . ')'; } } public function supportsIdentityColumns() { return true; } public function prefersIdentityColumns() { return true; } /** * Gets the character casing of a column in an SQL result set of this platform. * * DB2 returns all column names in SQL result sets in uppercase. * * @param string $column The column name for which to get the correct character casing. * @return string The column name in the character casing used in SQL result sets. */ public function getSQLResultCasing($column) { return strtoupper($column); } public function getForUpdateSQL() { return ' WITH RR USE AND KEEP UPDATE LOCKS'; } public function getDummySelectSQL() { return 'SELECT 1 FROM sysibm.sysdummy1'; } /** * DB2 supports savepoints, but they work semantically different than on other vendor platforms. * * TODO: We have to investigate how to get DB2 up and running with savepoints. * * @return bool */ public function supportsSavepoints() { return false; } }. */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\DBALException, Doctrine\DBAL\Schema\TableDiff; /** * The MySqlPlatform provides the behavior, features and SQL dialect of the * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that * uses the InnoDB storage engine. * * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei * @todo Rename: MySQLPlatform */ class MySqlPlatform extends AbstractPlatform { /** * Gets the character used for identifier quoting. * * @return string * @override */ public function getIdentifierQuoteCharacter() { return '`'; } /** * Returns the regular expression operator. * * @return string * @override */ public function getRegexpExpression() { return 'RLIKE'; } /** * Returns global unique identifier * * @return string to get global unique identifier * @override */ public function getGuidExpression() { return 'UUID()'; } /** * returns the position of the first occurrence of substring $substr in string $str * * @param string $substr literal string to find * @param string $str literal string * @param int $pos position to start at, beginning of string by default * @return integer */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos == false) { return 'LOCATE(' . $substr . ', ' . $str . ')'; } else { return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; } } /** * Returns a series of strings concatinated * * concat() accepts an arbitrary number of parameters. Each parameter * must contain an expression or an array with expressions. * * @param string|array(string) strings that will be concatinated. * @override */ public function getConcatExpression() { $args = func_get_args(); return 'CONCAT(' . join(', ', (array) $args) . ')'; } public function getListDatabasesSQL() { return 'SHOW DATABASES'; } public function getListTableConstraintsSQL($table) { return 'SHOW INDEX FROM ' . $table; } public function getListTableIndexesSQL($table) { return 'SHOW INDEX FROM ' . $table; } public function getListViewsSQL($database) { return "SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = '".$database."'"; } public function getListTableForeignKeysSQL($table, $database = null) { $sql = "SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ". "k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ". "FROM information_schema.key_column_usage k /*!50116 ". "INNER JOIN information_schema.referential_constraints c ON ". " c.constraint_name = k.constraint_name AND ". " c.table_name = '$table' */ WHERE k.table_name = '$table'"; if ($database) { $sql .= " AND k.table_schema = '$database' /*!50116 AND c.constraint_schema = '$database' */"; } $sql .= " AND k.`REFERENCED_COLUMN_NAME` is not NULL"; return $sql; } public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } public function getDropViewSQL($name) { return 'DROP VIEW '. $name; } /** * Gets the SQL snippet used to declare a VARCHAR column on the MySql platform. * * @params array $field */ public function getVarcharTypeDeclarationSQL(array $field) { if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->getVarcharMaxLength(); } else { $field['length'] = false; } } $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); } /** @override */ public function getClobTypeDeclarationSQL(array $field) { if ( ! empty($field['length']) && is_numeric($field['length'])) { $length = $field['length']; if ($length <= 255) { return 'TINYTEXT'; } else if ($length <= 65532) { return 'TEXT'; } else if ($length <= 16777215) { return 'MEDIUMTEXT'; } } return 'LONGTEXT'; } /** * @override */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { return 'TIMESTAMP'; } else { return 'DATETIME'; } } /** * @override */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * @override */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIME'; } /** * @override */ public function getBooleanTypeDeclarationSQL(array $field) { return 'TINYINT(1)'; } /** * 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. */ public function getCollationFieldDeclaration($collation) { return 'COLLATE ' . $collation; } /** * Whether the platform prefers identity columns for ID generation. * MySql prefers "autoincrement" identity columns since sequences can only * be emulated with a table. * * @return boolean * @override */ public function prefersIdentityColumns() { return true; } /** * Whether the platform supports identity columns. * MySql supports this through AUTO_INCREMENT columns. * * @return boolean * @override */ public function supportsIdentityColumns() { return true; } public function getShowDatabasesSQL() { return 'SHOW DATABASES'; } public function getListTablesSQL() { return 'SHOW FULL TABLES WHERE Table_type = "BASE TABLE"'; } public function getListTableColumnsSQL($table) { return 'DESCRIBE ' . $table; } /** * create a new database * * @param string $name name of the database that should be created * @return string * @override */ public function getCreateDatabaseSQL($name) { return 'CREATE DATABASE ' . $name; } /** * drop an existing database * * @param string $name name of the database that should be dropped * @return string * @override */ public function getDropDatabaseSQL($name) { return 'DROP DATABASE ' . $name; } /** * create a new table * * @param string $tableName Name of the database that should be created * @param array $columns 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: * array( * 'comment' => 'Foo', * 'charset' => 'utf8', * 'collate' => 'utf8_unicode_ci', * 'type' => 'innodb', * ); * * @return void * @override */ protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) { $queryFields = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $index => $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); } } // add all indexes if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach($options['indexes'] as $index => $definition) { $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } // attach all primary keys if (isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } $query = 'CREATE '; if (!empty($options['temporary'])) { $query .= 'TEMPORARY '; } $query.= 'TABLE ' . $tableName . ' (' . $queryFields . ')'; $optionStrings = array(); if (isset($options['comment'])) { $optionStrings['comment'] = 'COMMENT = ' . $this->quote($options['comment'], 'text'); } if (isset($options['charset'])) { $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset']; if (isset($options['collate'])) { $optionStrings['charset'] .= ' COLLATE ' . $options['collate']; } } // get the type of the table if (isset($options['engine'])) { $optionStrings[] = 'ENGINE = ' . $options['engine']; } else { // default to innodb $optionStrings[] = 'ENGINE = InnoDB'; } if ( ! empty($optionStrings)) { $query.= ' '.implode(' ', $optionStrings); } $sql[] = $query; if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] as $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); } } return $sql; } /** * Gets the SQL to alter an existing table. * * @param TableDiff $diff * @return array */ public function getAlterTableSQL(TableDiff $diff) { $queryParts = array(); if ($diff->newName !== false) { $queryParts[] = 'RENAME TO ' . $diff->newName; } foreach ($diff->addedColumns AS $fieldName => $column) { $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } foreach ($diff->removedColumns AS $column) { $queryParts[] = 'DROP ' . $column->getName(); } foreach ($diff->changedColumns AS $columnDiff) { /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */ $column = $columnDiff->column; $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } foreach ($diff->renamedColumns AS $oldColumnName => $column) { $queryParts[] = 'CHANGE ' . $oldColumnName . ' ' . $this->getColumnDeclarationSQL($column->getName(), $column->toArray()); } $sql = array(); if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(", ", $queryParts); } $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); return $sql; } /** * Obtain DBMS specific SQL code portion needed to declare an integer type * field to be used in statements like CREATE TABLE. * * @param string $name name the field to be declared. * @param string $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: * * unsigned * Boolean flag that indicates whether the field * should be declared as unsigned integer if * possible. * * default * Integer 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. * @return string DBMS specific SQL code portion that should be used to * declare the specified field. * @override */ public function getIntegerTypeDeclarationSQL(array $field) { return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); } /** @override */ public function getBigIntTypeDeclarationSQL(array $field) { return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); } /** @override */ public function getSmallIntTypeDeclarationSQL(array $field) { return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); } /** @override */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { $autoinc = ''; if ( ! empty($columnDef['autoincrement'])) { $autoinc = ' AUTO_INCREMENT'; } $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; return $unsigned . $autoinc; } /** * Return the FOREIGN KEY query section dealing with non-standard options * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... * * @param ForeignKeyConstraint $foreignKey * @return string * @override */ public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey) { $query = ''; if ($foreignKey->hasOption('match')) { $query .= ' MATCH ' . $foreignKey->getOption('match'); } $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); return $query; } /** * Gets the SQL to drop an index of a table. * * @param Index $index name of the index to be dropped * @param string|Table $table name of table that should be used in method * @override */ public function getDropIndexSQL($index, $table=null) { if($index instanceof \Doctrine\DBAL\Schema\Index) { $index = $index->getName(); } else if(!is_string($index)) { throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); } if($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } else if(!is_string($table)) { throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); } return 'DROP INDEX ' . $index . ' ON ' . $table; } /** * Gets the SQL to drop a table. * * @param string $table The name of table to drop. * @override */ public function getDropTableSQL($table) { if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } else if(!is_string($table)) { throw new \InvalidArgumentException('MysqlPlatform::getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); } return 'DROP TABLE ' . $table; } public function getSetTransactionIsolationSQL($level) { return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); } /** * Get the platform name for this instance. * * @return string */ public function getName() { return 'mysql'; } public function getReadLockSQL() { return 'LOCK IN SHARE MODE'; } protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = array( 'tinyint' => 'boolean', 'smallint' => 'smallint', 'mediumint' => 'integer', 'int' => 'integer', 'integer' => 'integer', 'bigint' => 'bigint', 'tinytext' => 'text', 'mediumtext' => 'text', 'longtext' => 'text', 'text' => 'text', 'varchar' => 'string', 'string' => 'string', 'char' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'timestamp' => 'datetime', 'time' => 'time', 'float' => 'decimal', 'double' => 'decimal', 'real' => 'decimal', 'decimal' => 'decimal', 'numeric' => 'decimal', 'year' => 'date', ); } } . */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\DBALException, Doctrine\DBAL\Connection, Doctrine\DBAL\Types, Doctrine\DBAL\Schema\Table, Doctrine\DBAL\Schema\Index, Doctrine\DBAL\Schema\ForeignKeyConstraint, Doctrine\DBAL\Schema\TableDiff; /** * Base class for all DatabasePlatforms. The DatabasePlatforms are the central * point of abstraction of platform-specific behaviors, features and SQL dialects. * They are a passive source of information. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Lukas Smith (PEAR MDB2 library) * @author Benjamin Eberlei * @todo Remove any unnecessary methods. */ abstract class AbstractPlatform { /** * @var int */ const CREATE_INDEXES = 1; /** * @var int */ const CREATE_FOREIGNKEYS = 2; /** * @var int */ const TRIM_UNSPECIFIED = 0; /** * @var int */ const TRIM_LEADING = 1; /** * @var int */ const TRIM_TRAILING = 2; /** * @var int */ const TRIM_BOTH = 3; /** * @var array */ protected $doctrineTypeMapping = null; /** * Constructor. */ public function __construct() {} /** * Gets the SQL snippet that declares a boolean column. * * @param array $columnDef * @return string */ abstract public function getBooleanTypeDeclarationSQL(array $columnDef); /** * Gets the SQL snippet that declares a 4 byte integer column. * * @param array $columnDef * @return string */ abstract public function getIntegerTypeDeclarationSQL(array $columnDef); /** * Gets the SQL snippet that declares an 8 byte integer column. * * @param array $columnDef * @return string */ abstract public function getBigIntTypeDeclarationSQL(array $columnDef); /** * Gets the SQL snippet that declares a 2 byte integer column. * * @param array $columnDef * @return string */ abstract public function getSmallIntTypeDeclarationSQL(array $columnDef); /** * Gets the SQL snippet that declares common properties of an integer column. * * @param array $columnDef * @return string */ abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef); /** * Lazy load Doctrine Type Mappings * * @return void */ abstract protected function initializeDoctrineTypeMappings(); /** * Gets the SQL snippet used to declare a VARCHAR column type. * * @param array $field */ abstract public function getVarcharTypeDeclarationSQL(array $field); /** * Gets the SQL snippet used to declare a CLOB column type. * * @param array $field */ abstract public function getClobTypeDeclarationSQL(array $field); /** * Gets the name of the platform. * * @return string */ abstract public function getName(); /** * Register a doctrine type to be used in conjunction with a column type of this platform. * * @param string $dbType * @param string $doctrineType */ public function registerDoctrineTypeMapping($dbType, $doctrineType) { if ($this->doctrineTypeMapping === null) { $this->initializeDoctrineTypeMappings(); } if (!Types\Type::hasType($doctrineType)) { throw DBALException::typeNotFound($doctrineType); } $dbType = strtolower($dbType); $this->doctrineTypeMapping[$dbType] = $doctrineType; } /** * Get the Doctrine type that is mapped for the given database column type. * * @param string $dbType * @return string */ public function getDoctrineTypeMapping($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeDoctrineTypeMappings(); } $dbType = strtolower($dbType); if (isset($this->doctrineTypeMapping[$dbType])) { return $this->doctrineTypeMapping[$dbType]; } else { throw new \Doctrine\DBAL\DBALException("Unknown database type ".$dbType." requested, " . get_class($this) . " may not support it."); } } /** * Check if a database type is currently supported by this platform. * * @param string $dbType * @return bool */ public function hasDoctrineTypeMappingFor($dbType) { if ($this->doctrineTypeMapping === null) { $this->initializeDoctrineTypeMappings(); } $dbType = strtolower($dbType); return isset($this->doctrineTypeMapping[$dbType]); } /** * Gets the character used for identifier quoting. * * @return string */ public function getIdentifierQuoteCharacter() { return '"'; } /** * Gets the string portion that starts an SQL comment. * * @return string */ public function getSqlCommentStartString() { return "--"; } /** * Gets the string portion that ends an SQL comment. * * @return string */ public function getSqlCommentEndString() { return "\n"; } /** * Gets the maximum length of a varchar field. * * @return integer */ public function getVarcharMaxLength() { return 255; } /** * Gets all SQL wildcard characters of the platform. * * @return array */ public function getWildcards() { return array('%', '_'); } /** * Returns the regular expression operator. * * @return string */ public function getRegexpExpression() { throw DBALException::notSupported(__METHOD__); } /** * Returns the average value of a column * * @param string $column the column to use * @return string generated sql including an AVG aggregate function */ public function getAvgExpression($column) { return 'AVG(' . $column . ')'; } /** * Returns the number of rows (without a NULL value) of a column * * If a '*' is used instead of a column the number of selected rows * is returned. * * @param string|integer $column the column to use * @return string generated sql including a COUNT aggregate function */ public function getCountExpression($column) { return 'COUNT(' . $column . ')'; } /** * Returns the highest value of a column * * @param string $column the column to use * @return string generated sql including a MAX aggregate function */ public function getMaxExpression($column) { return 'MAX(' . $column . ')'; } /** * Returns the lowest value of a column * * @param string $column the column to use * @return string */ public function getMinExpression($column) { return 'MIN(' . $column . ')'; } /** * Returns the total sum of a column * * @param string $column the column to use * @return string */ public function getSumExpression($column) { return 'SUM(' . $column . ')'; } // scalar functions /** * Returns the md5 sum of a field. * * Note: Not SQL92, but common functionality * * @return string */ public function getMd5Expression($column) { return 'MD5(' . $column . ')'; } /** * Returns the length of a text field. * * @param string $expression1 * @param string $expression2 * @return string */ public function getLengthExpression($column) { return 'LENGTH(' . $column . ')'; } /** * Rounds a numeric field to the number of decimals specified. * * @param string $expression1 * @param string $expression2 * @return string */ public function getRoundExpression($column, $decimals = 0) { return 'ROUND(' . $column . ', ' . $decimals . ')'; } /** * Returns the remainder of the division operation * $expression1 / $expression2. * * @param string $expression1 * @param string $expression2 * @return string */ public function getModExpression($expression1, $expression2) { return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; } /** * Trim a string, leading/trailing/both and with a given char which defaults to space. * * @param string $str * @param int $pos * @param string $char has to be quoted already * @return string */ public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) { $posStr = ''; $trimChar = ($char != false) ? $char . ' FROM ' : ''; if ($pos == self::TRIM_LEADING) { $posStr = 'LEADING '.$trimChar; } else if($pos == self::TRIM_TRAILING) { $posStr = 'TRAILING '.$trimChar; } else if($pos == self::TRIM_BOTH) { $posStr = 'BOTH '.$trimChar; } return 'TRIM(' . $posStr . $str . ')'; } /** * rtrim * returns the string $str with proceeding space characters removed * * @param string $str literal string or column name * @return string */ public function getRtrimExpression($str) { return 'RTRIM(' . $str . ')'; } /** * ltrim * returns the string $str with leading space characters removed * * @param string $str literal string or column name * @return string */ public function getLtrimExpression($str) { return 'LTRIM(' . $str . ')'; } /** * upper * Returns the string $str with all characters changed to * uppercase according to the current character set mapping. * * @param string $str literal string or column name * @return string */ public function getUpperExpression($str) { return 'UPPER(' . $str . ')'; } /** * lower * Returns the string $str with all characters changed to * lowercase according to the current character set mapping. * * @param string $str literal string or column name * @return string */ public function getLowerExpression($str) { return 'LOWER(' . $str . ')'; } /** * returns the position of the first occurrence of substring $substr in string $str * * @param string $substr literal string to find * @param string $str literal string * @param int $pos position to start at, beginning of string by default * @return integer */ public function getLocateExpression($str, $substr, $startPos = false) { throw DBALException::notSupported(__METHOD__); } /** * Returns the current system date. * * @return string */ public function getNowExpression() { return 'NOW()'; } /** * return string to call a function to get a substring inside an SQL statement * * Note: Not SQL92, but common functionality. * * SQLite only supports the 2 parameter variant of this function * * @param string $value an sql string literal or column name/alias * @param integer $from where to start the substring portion * @param integer $len the substring portion length * @return string */ public function getSubstringExpression($value, $from, $len = null) { if ($len === null) return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; else { return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')'; } } /** * Returns a series of strings concatinated * * concat() accepts an arbitrary number of parameters. Each parameter * must contain an expression * * @param string $arg1, $arg2 ... $argN strings that will be concatinated. * @return string */ public function getConcatExpression() { return join(' || ' , func_get_args()); } /** * Returns the SQL for a logical not. * * Example: * * $q = new Doctrine_Query(); * $e = $q->expr; * $q->select('*')->from('table') * ->where($e->eq('id', $e->not('null')); * * * @return string a logical expression */ public function getNotExpression($expression) { return 'NOT(' . $expression . ')'; } /** * Returns the SQL to check if a value is one in a set of * given values. * * in() accepts an arbitrary number of parameters. The first parameter * must always specify the value that should be matched against. Successive * must contain a logical expression or an array with logical expressions. * These expressions will be matched against the first parameter. * * @param string $column the value that should be matched against * @param string|array(string) values that will be matched against $column * @return string logical expression */ public function getInExpression($column, $values) { if ( ! is_array($values)) { $values = array($values); } $values = $this->getIdentifiers($values); if (count($values) == 0) { throw \InvalidArgumentException('Values must not be empty.'); } return $column . ' IN (' . implode(', ', $values) . ')'; } /** * Returns SQL that checks if a expression is null. * * @param string $expression the expression that should be compared to null * @return string logical expression */ public function getIsNullExpression($expression) { return $expression . ' IS NULL'; } /** * Returns SQL that checks if a expression is not null. * * @param string $expression the expression that should be compared to null * @return string logical expression */ public function getIsNotNullExpression($expression) { return $expression . ' IS NOT NULL'; } /** * Returns SQL that checks if an expression evaluates to a value between * two values. * * The parameter $expression is checked if it is between $value1 and $value2. * * Note: There is a slight difference in the way BETWEEN works on some databases. * http://www.w3schools.com/sql/sql_between.asp. If you want complete database * independence you should avoid using between(). * * @param string $expression the value to compare to * @param string $value1 the lower value to compare with * @param string $value2 the higher value to compare with * @return string logical expression */ public function getBetweenExpression($expression, $value1, $value2) { return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2; } public function getAcosExpression($value) { return 'ACOS(' . $value . ')'; } public function getSinExpression($value) { return 'SIN(' . $value . ')'; } public function getPiExpression() { return 'PI()'; } public function getCosExpression($value) { return 'COS(' . $value . ')'; } public function getForUpdateSQL() { return 'FOR UPDATE'; } /** * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification. * * @param string $fromClause * @param int $lockMode * @return string */ public function appendLockHint($fromClause, $lockMode) { return $fromClause; } /** * Get the sql snippet to append to any SELECT statement which locks rows in shared read lock. * * This defaults to the ASNI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database * vendors allow to lighten this constraint up to be a real read lock. * * @return string */ public function getReadLockSQL() { return $this->getForUpdateSQL(); } /** * Get the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. * * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ASNI SQL standard. * * @return string */ public function getWriteLockSQL() { return $this->getForUpdateSQL(); } public function getDropDatabaseSQL($database) { return 'DROP DATABASE ' . $database; } /** * Drop a Table * * @param Table|string $table * @return string */ public function getDropTableSQL($table) { if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } return 'DROP TABLE ' . $table; } /** * Drop index from a table * * @param Index|string $name * @param string|Table $table * @return string */ public function getDropIndexSQL($index, $table=null) { if($index instanceof \Doctrine\DBAL\Schema\Index) { $index = $index->getName(); } else if(!is_string($index)) { throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); } return 'DROP INDEX ' . $index; } /** * Get drop constraint sql * * @param \Doctrine\DBAL\Schema\Constraint $constraint * @param string|Table $table * @return string */ public function getDropConstraintSQL($constraint, $table) { if ($constraint instanceof \Doctrine\DBAL\Schema\Constraint) { $constraint = $constraint->getName(); } if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; } /** * @param ForeignKeyConstraint|string $foreignKey * @param Table|string $table * @return string */ public function getDropForeignKeySQL($foreignKey, $table) { if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) { $foreignKey = $foreignKey->getName(); } if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; } /** * Gets the SQL statement(s) to create a table with the specified name, columns and constraints * on this platform. * * @param string $table The name of the table. * @param int $createFlags * @return array The sequence of SQL statements. */ public function getCreateTableSQL(Table $table, $createFlags=self::CREATE_INDEXES) { if ( ! is_int($createFlags)) { throw new \InvalidArgumentException("Second argument of AbstractPlatform::getCreateTableSQL() has to be integer."); } if (count($table->getColumns()) == 0) { throw DBALException::noColumnsSpecifiedForTable($table->getName()); } $tableName = $table->getName(); $options = $table->getOptions(); $options['uniqueConstraints'] = array(); $options['indexes'] = array(); $options['primary'] = array(); if (($createFlags&self::CREATE_INDEXES) > 0) { foreach ($table->getIndexes() AS $index) { /* @var $index Index */ if ($index->isPrimary()) { $options['primary'] = $index->getColumns(); } else { $options['indexes'][$index->getName()] = $index; } } } $columns = array(); foreach ($table->getColumns() AS $column) { /* @var \Doctrine\DBAL\Schema\Column $column */ $columnData = array(); $columnData['name'] = $column->getName(); $columnData['type'] = $column->getType(); $columnData['length'] = $column->getLength(); $columnData['notnull'] = $column->getNotNull(); $columnData['unique'] = false; // TODO: what do we do about this? $columnData['version'] = ($column->hasPlatformOption("version"))?$column->getPlatformOption('version'):false; if(strtolower($columnData['type']) == "string" && $columnData['length'] === null) { $columnData['length'] = 255; } $columnData['precision'] = $column->getPrecision(); $columnData['scale'] = $column->getScale(); $columnData['default'] = $column->getDefault(); $columnData['columnDefinition'] = $column->getColumnDefinition(); $columnData['autoincrement'] = $column->getAutoincrement(); if(in_array($column->getName(), $options['primary'])) { $columnData['primary'] = true; } $columns[$columnData['name']] = $columnData; } if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) { $options['foreignKeys'] = array(); foreach ($table->getForeignKeys() AS $fkConstraint) { $options['foreignKeys'][] = $fkConstraint; } } return $this->_getCreateTableSQL($tableName, $columns, $options); } /** * @param string $tableName * @param array $columns * @param array $options * @return array */ protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) { $columnListSql = $this->getColumnDeclarationListSQL($columns); if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $name => $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); } } if (isset($options['primary']) && ! empty($options['primary'])) { $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach($options['indexes'] as $index => $definition) { $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); } } $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; $check = $this->getCheckDeclarationSQL($columns); if ( ! empty($check)) { $query .= ', ' . $check; } $query .= ')'; $sql[] = $query; if (isset($options['foreignKeys'])) { foreach ((array) $options['foreignKeys'] AS $definition) { $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); } } return $sql; } public function getCreateTemporaryTableSnippetSQL() { return "CREATE TEMPORARY TABLE"; } /** * Gets the SQL to create a sequence on this platform. * * @param \Doctrine\DBAL\Schema\Sequence $sequence * @throws DBALException */ public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence) { throw DBALException::notSupported(__METHOD__); } /** * Gets the SQL to create a constraint on a table on this platform. * * @param Constraint $constraint * @param string|Table $table * @return string */ public function getCreateConstraintSQL(\Doctrine\DBAL\Schema\Constraint $constraint, $table) { if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getName(); $columns = array(); foreach ($constraint->getColumns() as $column) { $columns[] = $column; } $columnList = '('. implode(', ', $columns) . ')'; $referencesClause = ''; if ($constraint instanceof \Doctrine\DBAL\Schema\Index) { if($constraint->isPrimary()) { $query .= ' PRIMARY KEY'; } elseif ($constraint->isUnique()) { $query .= ' UNIQUE'; } else { throw new \InvalidArgumentException( 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().' ); } } else if ($constraint instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) { $query .= ' FOREIGN KEY'; $foreignColumns = array(); foreach ($constraint->getForeignColumns() AS $column) { $foreignColumns[] = $column; } $referencesClause = ' REFERENCES '.$constraint->getForeignTableName(). ' ('.implode(', ', $foreignColumns).')'; } $query .= ' '.$columnList.$referencesClause; return $query; } /** * Gets the SQL to create an index on a table on this platform. * * @param Index $index * @param string|Table $table name of the table on which the index is to be created * @return string */ public function getCreateIndexSQL(Index $index, $table) { if ($table instanceof Table) { $table = $table->getName(); } $name = $index->getName(); $columns = $index->getColumns(); if (count($columns) == 0) { throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); } $type = ''; if ($index->isUnique()) { $type = 'UNIQUE '; } $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')'; return $query; } /** * Quotes a string so that it can be safely used as a table or column name, * even if it is a reserved word of the platform. * * NOTE: Just because you CAN use quoted identifiers doesn't mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str identifier name to be quoted * @return string quoted identifier string */ public function quoteIdentifier($str) { $c = $this->getIdentifierQuoteCharacter(); return $c . $str . $c; } /** * Create a new foreign key * * @param ForeignKeyConstraint $foreignKey ForeignKey instance * @param string|Table $table name of the table on which the foreign key is to be created * @return string */ public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) { if ($table instanceof \Doctrine\DBAL\Schema\Table) { $table = $table->getName(); } $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); return $query; } /** * Gets the sql statements for altering an existing table. * * The method returns an array of sql statements, since some platforms need several statements. * * @param TableDiff $diff * @return array */ public function getAlterTableSQL(TableDiff $diff) { throw DBALException::notSupported(__METHOD__); } /** * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions. * * @param TableDiff $diff * @return array */ protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff) { if ($diff->newName !== false) { $tableName = $diff->newName; } else { $tableName = $diff->name; } $sql = array(); if ($this->supportsForeignKeyConstraints()) { foreach ($diff->removedForeignKeys AS $foreignKey) { $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); } foreach ($diff->addedForeignKeys AS $foreignKey) { $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); } foreach ($diff->changedForeignKeys AS $foreignKey) { $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); } } foreach ($diff->addedIndexes AS $index) { $sql[] = $this->getCreateIndexSQL($index, $tableName); } foreach ($diff->removedIndexes AS $index) { $sql[] = $this->getDropIndexSQL($index, $tableName); } foreach ($diff->changedIndexes AS $index) { $sql[] = $this->getDropIndexSQL($index, $tableName); $sql[] = $this->getCreateIndexSQL($index, $tableName); } return $sql; } /** * Get declaration of a number of fields in bulk * * @param array $fields a multidimensional associative array. * 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: * * 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. * unique * unique constraint * * @return string */ public function getColumnDeclarationListSQL(array $fields) { $queryFields = array(); foreach ($fields as $fieldName => $field) { $query = $this->getColumnDeclarationSQL($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. * unique * unique constraint * check * column check constraint * columnDefinition * a string that defines the complete column * * @return string DBMS specific SQL code portion that should be used to declare the column. */ public function getColumnDeclarationSQL($name, array $field) { if (isset($field['columnDefinition'])) { $columnDef = $this->getCustomTypeDeclarationSQL($field); } else { $default = $this->getDefaultValueDeclarationSQL($field); $charset = (isset($field['charset']) && $field['charset']) ? ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : ''; $collation = (isset($field['collation']) && $field['collation']) ? ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : ''; $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; $unique = (isset($field['unique']) && $field['unique']) ? ' ' . $this->getUniqueFieldDeclarationSQL() : ''; $check = (isset($field['check']) && $field['check']) ? ' ' . $field['check'] : ''; $typeDecl = $field['type']->getSqlDeclaration($field, $this); $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; } return $name . ' ' . $columnDef; } /** * Gets the SQL snippet that declares a floating point column of arbitrary precision. * * @param array $columnDef * @return string */ public function getDecimalTypeDeclarationSQL(array $columnDef) { $columnDef['precision'] = ( ! isset($columnDef['precision']) || empty($columnDef['precision'])) ? 10 : $columnDef['precision']; $columnDef['scale'] = ( ! isset($columnDef['scale']) || empty($columnDef['scale'])) ? 0 : $columnDef['scale']; return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')'; } /** * Obtain DBMS specific SQL code portion needed to set a default value * declaration to be used in statements like CREATE TABLE. * * @param array $field field definition array * @return string DBMS specific SQL code portion needed to set a default value */ public function getDefaultValueDeclarationSQL($field) { $default = empty($field['notnull']) ? ' DEFAULT NULL' : ''; if (isset($field['default'])) { $default = " DEFAULT '".$field['default']."'"; if (isset($field['type'])) { if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) { $default = " DEFAULT ".$field['default']; } else if ((string)$field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL()) { $default = " DEFAULT ".$this->getCurrentTimestampSQL(); } } } return $default; } /** * Obtain DBMS specific SQL code portion needed to set a CHECK constraint * declaration to be used in statements like CREATE TABLE. * * @param array $definition check definition * @return string DBMS specific SQL code portion needed to set a CHECK constraint */ public function getCheckDeclarationSQL(array $definition) { $constraints = array(); foreach ($definition as $field => $def) { if (is_string($def)) { $constraints[] = 'CHECK (' . $def . ')'; } else { if (isset($def['min'])) { $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; } if (isset($def['max'])) { $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; } } } return implode(', ', $constraints); } /** * Obtain DBMS specific SQL code portion needed to set a unique * constraint declaration to be used in statements like CREATE TABLE. * * @param string $name name of the unique constraint * @param Index $index index definition * @return string DBMS specific SQL code portion needed * to set a constraint */ public function getUniqueConstraintDeclarationSQL($name, Index $index) { if (count($index->getColumns()) == 0) { throw \InvalidArgumentException("Incomplete definition. 'columns' required."); } return 'CONSTRAINT ' . $name . ' UNIQUE (' . $this->getIndexFieldDeclarationListSQL($index->getColumns()) . ')'; } /** * Obtain DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @param string $name name of the index * @param Index $index index definition * @return string DBMS specific SQL code portion needed to set an index */ public function getIndexDeclarationSQL($name, Index $index) { $type = ''; if($index->isUnique()) { $type = 'UNIQUE '; } if (count($index->getColumns()) == 0) { throw \InvalidArgumentException("Incomplete definition. 'columns' required."); } return $type . 'INDEX ' . $name . ' (' . $this->getIndexFieldDeclarationListSQL($index->getColumns()) . ')'; } /** * getCustomTypeDeclarationSql * Obtail SQL code portion needed to create a custom column, * e.g. when a field has the "columnDefinition" keyword. * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. * * @return string */ public function getCustomTypeDeclarationSQL(array $columnDef) { return $columnDef['columnDefinition']; } /** * getIndexFieldDeclarationList * Obtain DBMS specific SQL code portion needed to set an index * declaration to be used in statements like CREATE TABLE. * * @return string */ public function getIndexFieldDeclarationListSQL(array $fields) { $ret = array(); foreach ($fields as $field => $definition) { if (is_array($definition)) { $ret[] = $field; } else { $ret[] = $definition; } } return implode(', ', $ret); } /** * A method to return the required SQL string that fits between CREATE ... TABLE * to create the table as a temporary table. * * Should be overridden in driver classes to return the correct string for the * specific database type. * * The default is to return the string "TEMPORARY" - this will result in a * SQL error for any database that does not support temporary tables, or that * requires a different SQL command from "CREATE TEMPORARY TABLE". * * @return string The string required to be placed between "CREATE" and "TABLE" * to generate a temporary table, if possible. */ public function getTemporaryTableSQL() { return 'TEMPORARY'; } /** * Some vendors require temporary table names to be qualified specially. * * @param string $tableName * @return string */ public function getTemporaryTableName($tableName) { return $tableName; } /** * Get sql query to show a list of database. * * @return string */ public function getShowDatabasesSQL() { throw DBALException::notSupported(__METHOD__); } /** * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a field declaration to be used in statements like CREATE TABLE. * * @param array $definition an associative array with the following structure: * name optional constraint name * * local the local field(s) * * foreign the foreign reference field(s) * * foreignTable the name of the foreign table * * onDelete referential delete action * * onUpdate referential update action * * deferred deferred constraint checking * * The onDelete and onUpdate keys accept the following values: * * CASCADE: Delete or update the row from the parent table and automatically delete or * update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. * Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column * in the parent table or in the child table. * * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the * child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier * specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported. * * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary * key value is not allowed to proceed if there is a related foreign key value in the referenced table. * * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as * omitting the ON DELETE or ON UPDATE clause. * * SET DEFAULT * * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a field declaration. */ public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); return $sql; } /** * Return the FOREIGN KEY query section dealing with non-standard options * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... * * @param ForeignKeyConstraint $foreignKey foreign key definition * @return string */ public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) { $query = ''; if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) { $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); } if ($foreignKey->hasOption('onDelete')) { $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); } return $query; } /** * returns given referential action in uppercase if valid, otherwise throws * an exception * * @throws Doctrine_Exception_Exception if unknown referential action given * @param string $action foreign key referential action * @param string foreign key referential action in uppercase */ public function getForeignKeyReferentialActionSQL($action) { $upper = strtoupper($action); switch ($upper) { case 'CASCADE': case 'SET NULL': case 'NO ACTION': case 'RESTRICT': case 'SET DEFAULT': return $upper; break; default: throw \InvalidArgumentException('Invalid foreign key action: ' . $upper); } } /** * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint * of a field declaration to be used in statements like CREATE TABLE. * * @param ForeignKeyConstraint $foreignKey * @return string */ public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) { $sql = ''; if (strlen($foreignKey->getName())) { $sql .= 'CONSTRAINT ' . $foreignKey->getName() . ' '; } $sql .= 'FOREIGN KEY ('; if (count($foreignKey->getLocalColumns()) == 0) { throw new \InvalidArgumentException("Incomplete definition. 'local' required."); } if (count($foreignKey->getForeignColumns()) == 0) { throw new \InvalidArgumentException("Incomplete definition. 'foreign' required."); } if (strlen($foreignKey->getForeignTableName()) == 0) { throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required."); } $sql .= implode(', ', $foreignKey->getLocalColumns()) . ') REFERENCES ' . $foreignKey->getForeignTableName() . '(' . implode(', ', $foreignKey->getForeignColumns()) . ')'; return $sql; } /** * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint * of a field declaration to be used in statements like CREATE TABLE. * * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint * of a field declaration. */ public function getUniqueFieldDeclarationSQL() { return 'UNIQUE'; } /** * 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. */ public function getColumnCharsetDeclarationSQL($charset) { 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. */ public function getColumnCollationDeclarationSQL($collation) { return ''; } /** * Whether the platform prefers sequences for ID generation. * Subclasses should override this method to return TRUE if they prefer sequences. * * @return boolean */ public function prefersSequences() { return false; } /** * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. * Subclasses should override this method to return TRUE if they prefer identity columns. * * @return boolean */ public function prefersIdentityColumns() { return false; } /** * Some platforms need the boolean values to be converted. * * The default conversion in this implementation converts to integers (false => 0, true => 1). * * @param mixed $item */ public function convertBooleans($item) { if (is_array($item)) { foreach ($item as $k => $value) { if (is_bool($value)) { $item[$k] = (int) $value; } } } else if (is_bool($item)) { $item = (int) $item; } return $item; } /** * Gets the SQL statement specific for the platform to set the charset. * * This function is MySQL specific and required by * {@see \Doctrine\DBAL\Connection::setCharset($charset)} * * @param string $charset * @return string */ public function getSetCharsetSQL($charset) { return "SET NAMES '".$charset."'"; } /** * Gets the SQL specific for the platform to get the current date. * * @return string */ public function getCurrentDateSQL() { return 'CURRENT_DATE'; } /** * Gets the SQL specific for the platform to get the current time. * * @return string */ public function getCurrentTimeSQL() { return 'CURRENT_TIME'; } /** * Gets the SQL specific for the platform to get the current timestamp * * @return string */ public function getCurrentTimestampSQL() { return 'CURRENT_TIMESTAMP'; } /** * Get sql for transaction isolation level Connection constant * * @param integer $level */ protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case Connection::TRANSACTION_READ_UNCOMMITTED: return 'READ UNCOMMITTED'; case Connection::TRANSACTION_READ_COMMITTED: return 'READ COMMITTED'; case Connection::TRANSACTION_REPEATABLE_READ: return 'REPEATABLE READ'; case Connection::TRANSACTION_SERIALIZABLE: return 'SERIALIZABLE'; default: throw new \InvalidArgumentException('Invalid isolation level:' . $level); } } public function getListDatabasesSQL() { throw DBALException::notSupported(__METHOD__); } public function getListSequencesSQL($database) { throw DBALException::notSupported(__METHOD__); } public function getListTableConstraintsSQL($table) { throw DBALException::notSupported(__METHOD__); } public function getListTableColumnsSQL($table) { throw DBALException::notSupported(__METHOD__); } public function getListTablesSQL() { throw DBALException::notSupported(__METHOD__); } public function getListUsersSQL() { throw DBALException::notSupported(__METHOD__); } /** * Get the SQL to list all views of a database or user. * * @param string $database * @return string */ public function getListViewsSQL($database) { throw DBALException::notSupported(__METHOD__); } public function getListTableIndexesSQL($table) { throw DBALException::notSupported(__METHOD__); } public function getListTableForeignKeysSQL($table) { throw DBALException::notSupported(__METHOD__); } public function getCreateViewSQL($name, $sql) { throw DBALException::notSupported(__METHOD__); } public function getDropViewSQL($name) { throw DBALException::notSupported(__METHOD__); } public function getDropSequenceSQL($sequence) { throw DBALException::notSupported(__METHOD__); } public function getSequenceNextValSQL($sequenceName) { throw DBALException::notSupported(__METHOD__); } public function getCreateDatabaseSQL($database) { throw DBALException::notSupported(__METHOD__); } /** * Get sql to set the transaction isolation level * * @param integer $level */ public function getSetTransactionIsolationSQL($level) { throw DBALException::notSupported(__METHOD__); } /** * Obtain DBMS specific SQL to be used to create datetime fields in * statements like CREATE TABLE * * @param array $fieldDeclaration * @return string */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { throw DBALException::notSupported(__METHOD__); } /** * Obtain DBMS specific SQL to be used to create datetime with timezone offset fields. * * @param array $fieldDeclaration */ public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) { return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration); } /** * Obtain DBMS specific SQL to be used to create date fields in statements * like CREATE TABLE. * * @param array $fieldDeclaration * @return string */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { throw DBALException::notSupported(__METHOD__); } /** * Obtain DBMS specific SQL to be used to create time fields in statements * like CREATE TABLE. * * @param array $fieldDeclaration * @return string */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { throw DBALException::notSupported(__METHOD__); } public function getFloatDeclarationSQL(array $fieldDeclaration) { return 'DOUBLE PRECISION'; } /** * Gets the default transaction isolation level of the platform. * * @return integer The default isolation level. * @see Doctrine\DBAL\Connection\TRANSACTION_* constants. */ public function getDefaultTransactionIsolationLevel() { return Connection::TRANSACTION_READ_COMMITTED; } /* supports*() metods */ /** * Whether the platform supports sequences. * * @return boolean */ public function supportsSequences() { return false; } /** * Whether the platform supports identity columns. * Identity columns are columns that recieve an auto-generated value from the * database on insert of a row. * * @return boolean */ public function supportsIdentityColumns() { return false; } /** * Whether the platform supports indexes. * * @return boolean */ public function supportsIndexes() { return true; } public function supportsAlterTable() { return true; } /** * Whether the platform supports transactions. * * @return boolean */ public function supportsTransactions() { return true; } /** * Whether the platform supports savepoints. * * @return boolean */ public function supportsSavepoints() { return true; } /** * Whether the platform supports releasing savepoints. * * @return boolean */ public function supportsReleaseSavepoints() { return $this->supportsSavepoints(); } /** * Whether the platform supports primary key constraints. * * @return boolean */ public function supportsPrimaryConstraints() { return true; } /** * Does the platform supports foreign key constraints? * * @return boolean */ public function supportsForeignKeyConstraints() { return true; } /** * Does this platform supports onUpdate in foreign key constraints? * * @return bool */ public function supportsForeignKeyOnUpdate() { return ($this->supportsForeignKeyConstraints() && true); } /** * Whether the platform supports database schemas. * * @return boolean */ public function supportsSchemas() { return false; } /** * Some databases don't allow to create and drop databases at all or only with certain tools. * * @return bool */ public function supportsCreateDropDatabase() { return true; } /** * Whether the platform supports getting the affected rows of a recent * update/delete type query. * * @return boolean */ public function supportsGettingAffectedRows() { return true; } public function getIdentityColumnNullInsertSQL() { return ""; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime value of this platform. * * @return string The format string. */ public function getDateTimeFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored datetime with timezone value of this platform. * * @return string The format string. */ public function getDateTimeTzFormatString() { return 'Y-m-d H:i:s'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored date value of this platform. * * @return string The format string. */ public function getDateFormatString() { return 'Y-m-d'; } /** * Gets the format string, as accepted by the date() function, that describes * the format of a stored time value of this platform. * * @return string The format string. */ public function getTimeFormatString() { return 'H:i:s'; } public function modifyLimitQuery($query, $limit, $offset = null) { if ( ! is_null($limit)) { $query .= ' LIMIT ' . $limit; } if ( ! is_null($offset)) { $query .= ' OFFSET ' . $offset; } return $query; } /** * Gets the character casing of a column in an SQL result set of this platform. * * @param string $column The column name for which to get the correct character casing. * @return string The column name in the character casing used in SQL result sets. */ public function getSQLResultCasing($column) { return $column; } /** * Makes any fixes to a name of a schema element (table, sequence, ...) that are required * by restrictions of the platform, like a maximum length. * * @param string $schemaName * @return string */ public function fixSchemaElementName($schemaElementName) { return $schemaElementName; } /** * Maximum length of any given databse identifier, like tables or column names. * * @return int */ public function getMaxIdentifierLength() { return 63; } /** * Get the insert sql for an empty insert statement * * @param string $tableName * @param string $identifierColumnName * @return string $sql */ public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) { return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)'; } /** * Generate a Truncate Table SQL statement for a given table. * * Cascade is not supported on many platforms but would optionally cascade the truncate by * following the foreign keys. * * @param string $tableName * @param bool $cascade * @return string */ public function getTruncateTableSQL($tableName, $cascade = false) { return 'TRUNCATE '.$tableName; } /** * This is for test reasons, many vendors have special requirements for dummy statements. * * @return string */ public function getDummySelectSQL() { return 'SELECT 1'; } /** * Generate SQL to create a new savepoint * * @param string $savepoint * @return string */ public function createSavePoint($savepoint) { return 'SAVEPOINT ' . $savepoint; } /** * Generate SQL to release a savepoint * * @param string $savepoint * @return string */ public function releaseSavePoint($savepoint) { return 'RELEASE SAVEPOINT ' . $savepoint; } /** * Generate SQL to rollback a savepoint * * @param string $savepoint * @return string */ public function rollbackSavePoint($savepoint) { return 'ROLLBACK TO SAVEPOINT ' . $savepoint; } }. */ namespace Doctrine\DBAL\Platforms; use Doctrine\DBAL\DBALException; /** * The SqlitePlatform class describes the specifics and dialects of the SQLite * database platform. * * @since 2.0 * @author Roman Borschel * @author Benjamin Eberlei * @todo Rename: SQLitePlatform */ class SqlitePlatform extends AbstractPlatform { /** * returns the regular expression operator * * @return string * @override */ public function getRegexpExpression() { return 'RLIKE'; } /** * Return string to call a variable with the current timestamp inside an SQL statement * There are three special variables for current date and time. * * @return string sqlite function as string * @override */ public function getNowExpression($type = 'timestamp') { switch ($type) { case 'time': return 'time(\'now\')'; case 'date': return 'date(\'now\')'; case 'timestamp': default: return 'datetime(\'now\')'; } } /** * Trim a string, leading/trailing/both and with a given char which defaults to space. * * @param string $str * @param int $pos * @param string $char * @return string */ public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) { $trimFn = ''; $trimChar = ($char != false) ? (', ' . $char) : ''; if ($pos == self::TRIM_LEADING) { $trimFn = 'LTRIM'; } else if($pos == self::TRIM_TRAILING) { $trimFn = 'RTRIM'; } else { $trimFn = 'TRIM'; } return $trimFn . '(' . $str . $trimChar . ')'; } /** * return string to call a function to get a substring inside an SQL statement * * Note: Not SQL92, but common functionality. * * SQLite only supports the 2 parameter variant of this function * * @param string $value an sql string literal or column name/alias * @param integer $position where to start the substring portion * @param integer $length the substring portion length * @return string SQL substring function with given parameters * @override */ public function getSubstringExpression($value, $position, $length = null) { if ($length !== null) { return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')'; } return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))'; } /** * returns the position of the first occurrence of substring $substr in string $str * * @param string $substr literal string to find * @param string $str literal string * @param int $pos position to start at, beginning of string by default * @return integer */ public function getLocateExpression($str, $substr, $startPos = false) { if ($startPos == false) { return 'LOCATE('.$str.', '.$substr.')'; } else { return 'LOCATE('.$str.', '.$substr.', '.$startPos.')'; } } protected function _getTransactionIsolationLevelSQL($level) { switch ($level) { case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: return 0; case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: return 1; default: return parent::_getTransactionIsolationLevelSQL($level); } } public function getSetTransactionIsolationSQL($level) { return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); } /** * @override */ public function prefersIdentityColumns() { return true; } /** * @override */ public function getBooleanTypeDeclarationSQL(array $field) { return 'BOOLEAN'; } /** * @override */ public function getIntegerTypeDeclarationSQL(array $field) { return $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getBigIntTypeDeclarationSQL(array $field) { return $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getTinyIntTypeDeclarationSql(array $field) { return $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getSmallIntTypeDeclarationSQL(array $field) { return $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getMediumIntTypeDeclarationSql(array $field) { return $this->_getCommonIntegerTypeDeclarationSQL($field); } /** * @override */ public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'DATETIME'; } /** * @override */ public function getDateTypeDeclarationSQL(array $fieldDeclaration) { return 'DATE'; } /** * @override */ public function getTimeTypeDeclarationSQL(array $fieldDeclaration) { return 'TIME'; } /** * @override */ protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) { $autoinc = ! empty($columnDef['autoincrement']) ? ' AUTOINCREMENT' : ''; $pk = ! empty($columnDef['primary']) && ! empty($autoinc) ? ' PRIMARY KEY' : ''; return 'INTEGER' . $pk . $autoinc; } /** * 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 * @override */ protected function _getCreateTableSQL($name, array $columns, array $options = array()) { $queryFields = $this->getColumnDeclarationListSQL($columns); $autoinc = false; foreach($columns as $field) { if (isset($field['autoincrement']) && $field['autoincrement']) { $autoinc = true; break; } } if ( ! $autoinc && isset($options['primary']) && ! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $keyColumns = array_map(array($this, 'quoteIdentifier'), $keyColumns); $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')'; } $query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; if (isset($options['indexes']) && ! empty($options['indexes'])) { foreach ($options['indexes'] as $index => $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } if (isset($options['unique']) && ! empty($options['unique'])) { foreach ($options['unique'] as $index => $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } return $query; } /** * {@inheritdoc} */ public function getVarcharTypeDeclarationSQL(array $field) { if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { $field['length'] = $this->getVarcharMaxLength(); } else { $field['length'] = false; } } $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); } public function getClobTypeDeclarationSQL(array $field) { return 'CLOB'; } public function getListTableConstraintsSQL($table) { return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = '$table' AND sql NOT NULL ORDER BY name"; } public function getListTableColumnsSQL($table) { return "PRAGMA table_info($table)"; } public function getListTableIndexesSQL($table) { return "PRAGMA index_list($table)"; } public function getListTablesSQL() { return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' " . "UNION ALL SELECT name FROM sqlite_temp_master " . "WHERE type = 'table' ORDER BY name"; } public function getListViewsSQL($database) { return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; } public function getCreateViewSQL($name, $sql) { return 'CREATE VIEW ' . $name . ' AS ' . $sql; } public function getDropViewSQL($name) { return 'DROP VIEW '. $name; } /** * SQLite does support foreign key constraints, but only in CREATE TABLE statements... * This really limits their usefulness and requires SQLite specific handling, so * we simply say that SQLite does NOT support foreign keys for now... * * @return boolean FALSE * @override */ public function supportsForeignKeyConstraints() { return false; } public function supportsAlterTable() { return false; } public function supportsIdentityColumns() { return true; } /** * Get the platform name for this instance * * @return string */ public function getName() { return 'sqlite'; } /** * @inheritdoc */ public function getTruncateTableSQL($tableName, $cascade = false) { return 'DELETE FROM '.$tableName; } /** * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction() * * @param int|float $value * @return float */ static public function udfSqrt($value) { return sqrt($value); } /** * User-defined function for Sqlite that implements MOD(a, b) */ static public function udfMod($a, $b) { return ($a % $b); } /** * @param string $str * @param string $substr * @param int $offset */ static public function udfLocate($str, $substr, $offset = 0) { $pos = strpos($str, $substr, $offset); if ($pos !== false) { return $pos+1; } return 0; } public function getForUpdateSql() { return ''; } protected function initializeDoctrineTypeMappings() { $this->doctrineTypeMapping = array( 'boolean' => 'boolean', 'tinyint' => 'boolean', 'smallint' => 'smallint', 'mediumint' => 'integer', 'int' => 'integer', 'integer' => 'integer', 'serial' => 'integer', 'bigint' => 'bigint', 'bigserial' => 'bigint', 'clob' => 'text', 'tinytext' => 'text', 'mediumtext' => 'text', 'longtext' => 'text', 'text' => 'text', 'varchar' => 'string', 'varchar2' => 'string', 'nvarchar' => 'string', 'image' => 'string', 'ntext' => 'string', 'char' => 'string', 'date' => 'date', 'datetime' => 'datetime', 'timestamp' => 'datetime', 'time' => 'time', 'float' => 'float', 'double' => 'float', 'real' => 'float', 'decimal' => 'decimal', 'numeric' => 'decimal', ); } } . */ namespace Doctrine\DBAL; /** * Class to store and retrieve the version of Doctrine * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Version { /** * Current Doctrine Version */ const VERSION = '2.0.0RC1-DEV'; /** * Compares a Doctrine version with the current one. * * @param string $version Doctrine version to compare. * @return int Returns -1 if older, 0 if it is the same, 1 if version * passed as argument is newer. */ public static function compare($version) { $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); $version = str_replace(' ', '', $version); return version_compare($version, $currentVersion); } }. */ namespace Doctrine\DBAL; /** * Doctrine\DBAL\ConnectionException * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 4628 $ * @author Jonathan H. Wage . */ namespace Doctrine\DBAL; use PDO, Closure, Exception, Doctrine\DBAL\Types\Type, Doctrine\DBAL\Driver\Connection as DriverConnection, Doctrine\Common\EventManager, Doctrine\DBAL\DBALException; /** * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like * events, transaction isolation levels, configuration, emulated transaction nesting, * lazy connecting and more. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Konsta Vesterinen * @author Lukas Smith (MDB2 library) * @author Benjamin Eberlei */ class Connection implements DriverConnection { /** * Constant for transaction isolation level READ UNCOMMITTED. */ const TRANSACTION_READ_UNCOMMITTED = 1; /** * Constant for transaction isolation level READ COMMITTED. */ const TRANSACTION_READ_COMMITTED = 2; /** * Constant for transaction isolation level REPEATABLE READ. */ const TRANSACTION_REPEATABLE_READ = 3; /** * Constant for transaction isolation level SERIALIZABLE. */ const TRANSACTION_SERIALIZABLE = 4; /** * The wrapped driver connection. * * @var Doctrine\DBAL\Driver\Connection */ protected $_conn; /** * @var Doctrine\DBAL\Configuration */ protected $_config; /** * @var Doctrine\Common\EventManager */ protected $_eventManager; /** * Whether or not a connection has been established. * * @var boolean */ private $_isConnected = false; /** * The transaction nesting level. * * @var integer */ private $_transactionNestingLevel = 0; /** * The currently active transaction isolation level. * * @var integer */ private $_transactionIsolationLevel; /** * If nested transations should use savepoints * * @var integer */ private $_nestTransactionsWithSavepoints; /** * The parameters used during creation of the Connection instance. * * @var array */ private $_params = array(); /** * The DatabasePlatform object that provides information about the * database platform used by the connection. * * @var Doctrine\DBAL\Platforms\AbstractPlatform */ protected $_platform; /** * The schema manager. * * @var Doctrine\DBAL\Schema\SchemaManager */ protected $_schemaManager; /** * The used DBAL driver. * * @var Doctrine\DBAL\Driver */ protected $_driver; /** * Flag that indicates whether the current transaction is marked for rollback only. * * @var boolean */ private $_isRollbackOnly = false; /** * Initializes a new instance of the Connection class. * * @param array $params The connection parameters. * @param Driver $driver * @param Configuration $config * @param EventManager $eventManager */ public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) { $this->_driver = $driver; $this->_params = $params; if (isset($params['pdo'])) { $this->_conn = $params['pdo']; $this->_isConnected = true; } // Create default config and event manager if none given if ( ! $config) { $config = new Configuration(); } if ( ! $eventManager) { $eventManager = new EventManager(); } $this->_config = $config; $this->_eventManager = $eventManager; if ( ! isset($params['platform'])) { $this->_platform = $driver->getDatabasePlatform(); } else if ($params['platform'] instanceof Platforms\AbstractPlatform) { $this->_platform = $params['platform']; } else { throw DBALException::invalidPlatformSpecified(); } $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel(); } /** * Gets the parameters used during instantiation. * * @return array $params */ public function getParams() { return $this->_params; } /** * Gets the name of the database this Connection is connected to. * * @return string $database */ public function getDatabase() { return $this->_driver->getDatabase($this); } /** * Gets the hostname of the currently connected database. * * @return string */ public function getHost() { return isset($this->_params['host']) ? $this->_params['host'] : null; } /** * Gets the port of the currently connected database. * * @return mixed */ public function getPort() { return isset($this->_params['port']) ? $this->_params['port'] : null; } /** * Gets the username used by this connection. * * @return string */ public function getUsername() { return isset($this->_params['user']) ? $this->_params['user'] : null; } /** * Gets the password used by this connection. * * @return string */ public function getPassword() { return isset($this->_params['password']) ? $this->_params['password'] : null; } /** * Gets the DBAL driver instance. * * @return Doctrine\DBAL\Driver */ public function getDriver() { return $this->_driver; } /** * Gets the Configuration used by the Connection. * * @return Doctrine\DBAL\Configuration */ public function getConfiguration() { return $this->_config; } /** * Gets the EventManager used by the Connection. * * @return Doctrine\Common\EventManager */ public function getEventManager() { return $this->_eventManager; } /** * Gets the DatabasePlatform for the connection. * * @return Doctrine\DBAL\Platforms\AbstractPlatform */ public function getDatabasePlatform() { return $this->_platform; } /** * Establishes the connection with the database. * * @return boolean TRUE if the connection was successfully established, FALSE if * the connection is already open. */ public function connect() { if ($this->_isConnected) return false; $driverOptions = isset($this->_params['driverOptions']) ? $this->_params['driverOptions'] : array(); $user = isset($this->_params['user']) ? $this->_params['user'] : null; $password = isset($this->_params['password']) ? $this->_params['password'] : null; $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions); $this->_isConnected = true; if ($this->_eventManager->hasListeners(Events::postConnect)) { $eventArgs = new Event\ConnectionEventArgs($this); $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); } return true; } /** * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * * @param string $statement The SQL query. * @param array $params The query parameters. * @return array */ public function fetchAssoc($statement, array $params = array()) { return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC); } /** * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * * @param string $statement sql query to be executed * @param array $params prepared statement params * @return array */ public function fetchArray($statement, array $params = array()) { return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_NUM); } /** * Prepares and executes an SQL query and returns the value of a single column * of the first row of the result. * * @param string $statement sql query to be executed * @param array $params prepared statement params * @param int $colnum 0-indexed column number to retrieve * @return mixed */ public function fetchColumn($statement, array $params = array(), $colnum = 0) { return $this->executeQuery($statement, $params)->fetchColumn($colnum); } /** * Whether an actual connection to the database is established. * * @return boolean */ public function isConnected() { return $this->_isConnected; } /** * Checks whether a transaction is currently active. * * @return boolean TRUE if a transaction is currently active, FALSE otherwise. */ public function isTransactionActive() { return $this->_transactionNestingLevel > 0; } /** * Executes an SQL DELETE statement on a table. * * @param string $table The name of the table on which to delete. * @param array $identifier The deletion criteria. An associateve array containing column-value pairs. * @return integer The number of affected rows. */ public function delete($tableName, array $identifier) { $this->connect(); $criteria = array(); foreach (array_keys($identifier) as $columnName) { $criteria[] = $columnName . ' = ?'; } $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria); return $this->executeUpdate($query, array_values($identifier)); } /** * Closes the connection. * * @return void */ public function close() { unset($this->_conn); $this->_isConnected = false; } /** * Sets the transaction isolation level. * * @param integer $level The level to set. */ public function setTransactionIsolation($level) { $this->_transactionIsolationLevel = $level; return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level)); } /** * Gets the currently active transaction isolation level. * * @return integer The current transaction isolation level. */ public function getTransactionIsolation() { return $this->_transactionIsolationLevel; } /** * Executes an SQL UPDATE statement on a table. * * @param string $table The name of the table to update. * @param array $identifier The update criteria. An associative array containing column-value pairs. * @return integer The number of affected rows. */ public function update($tableName, array $data, array $identifier) { $this->connect(); $set = array(); foreach ($data as $columnName => $value) { $set[] = $columnName . ' = ?'; } $params = array_merge(array_values($data), array_values($identifier)); $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set) . ' WHERE ' . implode(' = ? AND ', array_keys($identifier)) . ' = ?'; return $this->executeUpdate($sql, $params); } /** * Inserts a table row with specified data. * * @param string $table The name of the table to insert data into. * @param array $data An associative array containing column-value pairs. * @return integer The number of affected rows. */ public function insert($tableName, array $data) { $this->connect(); // column names are specified as array keys $cols = array(); $placeholders = array(); foreach ($data as $columnName => $value) { $cols[] = $columnName; $placeholders[] = '?'; } $query = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $cols) . ')' . ' VALUES (' . implode(', ', $placeholders) . ')'; return $this->executeUpdate($query, array_values($data)); } /** * Sets the given charset on the current connection. * * @param string $charset The charset to set. */ public function setCharset($charset) { $this->executeUpdate($this->_platform->getSetCharsetSQL($charset)); } /** * Quote a string so it can be safely used as a table or column name, even if * it is a reserved name. * * Delimiting style depends on the underlying database platform that is being used. * * NOTE: Just because you CAN use quoted identifiers does not mean * you SHOULD use them. In general, they end up causing way more * problems than they solve. * * @param string $str The name to be quoted. * @return string The quoted name. */ public function quoteIdentifier($str) { return $this->_platform->quoteIdentifier($str); } /** * Quotes a given input parameter. * * @param mixed $input Parameter to be quoted. * @param string $type Type of the parameter. * @return string The quoted parameter. */ public function quote($input, $type = null) { $this->connect(); return $this->_conn->quote($input, $type); } /** * Prepares and executes an SQL query and returns the result as an associative array. * * @param string $sql The SQL query. * @param array $params The query parameters. * @return array */ public function fetchAll($sql, array $params = array()) { return $this->executeQuery($sql, $params)->fetchAll(PDO::FETCH_ASSOC); } /** * Prepares an SQL statement. * * @param string $statement The SQL statement to prepare. * @return Doctrine\DBAL\Driver\Statement The prepared statement. */ public function prepare($statement) { $this->connect(); return new Statement($statement, $this); } /** * Executes an, optionally parameterized, SQL query. * * If the query is parameterized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * * @param string $query The SQL query to execute. * @param array $params The parameters to bind to the query, if any. * @return Doctrine\DBAL\Driver\Statement The executed statement. * @internal PERF: Directly prepares a driver statement, not a wrapper. */ public function executeQuery($query, array $params = array(), $types = array()) { $this->connect(); $hasLogger = $this->_config->getSQLLogger() !== null; if ($hasLogger) { $this->_config->getSQLLogger()->startQuery($query, $params, $types); } if ($params) { $stmt = $this->_conn->prepare($query); if ($types) { $this->_bindTypedValues($stmt, $params, $types); $stmt->execute(); } else { $stmt->execute($params); } } else { $stmt = $this->_conn->query($query); } if ($hasLogger) { $this->_config->getSQLLogger()->stopQuery(); } return $stmt; } /** * Executes an, optionally parameterized, SQL query and returns the result, * applying a given projection/transformation function on each row of the result. * * @param string $query The SQL query to execute. * @param array $params The parameters, if any. * @param Closure $mapper The transformation function that is applied on each row. * The function receives a single paramater, an array, that * represents a row of the result set. * @return mixed The projected result of the query. */ public function project($query, array $params, Closure $function) { $result = array(); $stmt = $this->executeQuery($query, $params ?: array()); while ($row = $stmt->fetch()) { $result[] = $function($row); } $stmt->closeCursor(); return $result; } /** * Executes an SQL statement, returning a result set as a Statement object. * * @param string $statement * @param integer $fetchType * @return Doctrine\DBAL\Driver\Statement */ public function query() { $this->connect(); return call_user_func_array(array($this->_conn, 'query'), func_get_args()); } /** * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters * and returns the number of affected rows. * * This method supports PDO binding types as well as DBAL mapping types. * * @param string $query The SQL query. * @param array $params The query parameters. * @param array $types The parameter types. * @return integer The number of affected rows. * @internal PERF: Directly prepares a driver statement, not a wrapper. */ public function executeUpdate($query, array $params = array(), array $types = array()) { $this->connect(); $hasLogger = $this->_config->getSQLLogger() !== null; if ($hasLogger) { $this->_config->getSQLLogger()->startQuery($query, $params, $types); } if ($params) { $stmt = $this->_conn->prepare($query); if ($types) { $this->_bindTypedValues($stmt, $params, $types); $stmt->execute(); } else { $stmt->execute($params); } $result = $stmt->rowCount(); } else { $result = $this->_conn->exec($query); } if ($hasLogger) { $this->_config->getSQLLogger()->stopQuery(); } return $result; } /** * Execute an SQL statement and return the number of affected rows. * * @param string $statement * @return integer The number of affected rows. */ public function exec($statement) { $this->connect(); return $this->_conn->exec($statement); } /** * Returns the current transaction nesting level. * * @return integer The nesting level. A value of 0 means there's no active transaction. */ public function getTransactionNestingLevel() { return $this->_transactionNestingLevel; } /** * Fetch the SQLSTATE associated with the last database operation. * * @return integer The last error code. */ public function errorCode() { $this->connect(); return $this->_conn->errorCode(); } /** * Fetch extended error information associated with the last database operation. * * @return array The last error information. */ public function errorInfo() { $this->connect(); return $this->_conn->errorInfo(); } /** * Returns the ID of the last inserted row, or the last value from a sequence object, * depending on the underlying driver. * * Note: This method may not return a meaningful or consistent result across different drivers, * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY * columns or sequences. * * @param string $seqName Name of the sequence object from which the ID should be returned. * @return string A string representation of the last inserted ID. */ public function lastInsertId($seqName = null) { $this->connect(); return $this->_conn->lastInsertId($seqName); } /** * Executes a function in a transaction. * * The function gets passed this Connection instance as an (optional) parameter. * * If an exception occurs during execution of the function or transaction commit, * the transaction is rolled back and the exception re-thrown. * * @param Closure $func The function to execute transactionally. */ public function transactional(Closure $func) { $this->beginTransaction(); try { $func($this); $this->commit(); } catch (Exception $e) { $this->rollback(); throw $e; } } /** * Set if nested transactions should use savepoints * * @param boolean * @return void */ public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) { if ($this->_transactionNestingLevel > 0) { throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); } if (!$this->_platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints; } /** * Get if nested transactions should use savepoints * * @return boolean */ public function getNestTransactionsWithSavepoints() { return $this->_nestTransactionsWithSavepoints; } /** * Returns the savepoint name to use for nested transactions are false if they are not supported * "savepointFormat" parameter is not set * * @return mixed a string with the savepoint name or false */ protected function _getNestedTransactionSavePointName() { return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel; } /** * Starts a transaction by suspending auto-commit mode. * * @return void */ public function beginTransaction() { $this->connect(); ++$this->_transactionNestingLevel; if ($this->_transactionNestingLevel == 1) { $this->_conn->beginTransaction(); } else if ($this->_nestTransactionsWithSavepoints) { $this->createSavepoint($this->_getNestedTransactionSavePointName()); } } /** * Commits the current transaction. * * @return void * @throws ConnectionException If the commit failed due to no active transaction or * because the transaction was marked for rollback only. */ public function commit() { if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); } if ($this->_isRollbackOnly) { throw ConnectionException::commitFailedRollbackOnly(); } $this->connect(); if ($this->_transactionNestingLevel == 1) { $this->_conn->commit(); } else if ($this->_nestTransactionsWithSavepoints) { $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); } --$this->_transactionNestingLevel; } /** * Cancel any database changes done during the current transaction. * * this method can be listened with onPreTransactionRollback and onTransactionRollback * eventlistener methods * * @throws ConnectionException If the rollback operation failed. */ public function rollback() { if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); } $this->connect(); if ($this->_transactionNestingLevel == 1) { $this->_transactionNestingLevel = 0; $this->_conn->rollback(); $this->_isRollbackOnly = false; } else if ($this->_nestTransactionsWithSavepoints) { $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); --$this->_transactionNestingLevel; } else { $this->_isRollbackOnly = true; --$this->_transactionNestingLevel; } } /** * createSavepoint * creates a new savepoint * * @param string $savepoint name of a savepoint to set * @return void */ public function createSavepoint($savepoint) { if (!$this->_platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->_conn->exec($this->_platform->createSavePoint($savepoint)); } /** * releaseSavePoint * releases given savepoint * * @param string $savepoint name of a savepoint to release * @return void */ public function releaseSavepoint($savepoint) { if (!$this->_platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } if ($this->_platform->supportsReleaseSavepoints()) { $this->_conn->exec($this->_platform->releaseSavePoint($savepoint)); } } /** * rollbackSavePoint * releases given savepoint * * @param string $savepoint name of a savepoint to rollback to * @return void */ public function rollbackSavepoint($savepoint) { if (!$this->_platform->supportsSavepoints()) { throw ConnectionException::savepointsNotSupported(); } $this->_conn->exec($this->_platform->rollbackSavePoint($savepoint)); } /** * Gets the wrapped driver connection. * * @return Doctrine\DBAL\Driver\Connection */ public function getWrappedConnection() { $this->connect(); return $this->_conn; } /** * Gets the SchemaManager that can be used to inspect or change the * database schema through the connection. * * @return Doctrine\DBAL\Schema\SchemaManager */ public function getSchemaManager() { if ( ! $this->_schemaManager) { $this->_schemaManager = $this->_driver->getSchemaManager($this); } return $this->_schemaManager; } /** * Marks the current transaction so that the only possible * outcome for the transaction to be rolled back. * * @throws ConnectionException If no transaction is active. */ public function setRollbackOnly() { if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); } $this->_isRollbackOnly = true; } /** * Check whether the current transaction is marked for rollback only. * * @return boolean * @throws ConnectionException If no transaction is active. */ public function isRollbackOnly() { if ($this->_transactionNestingLevel == 0) { throw ConnectionException::noActiveTransaction(); } return $this->_isRollbackOnly; } /** * Converts a given value to its database representation according to the conversion * rules of a specific DBAL mapping type. * * @param mixed $value The value to convert. * @param string $type The name of the DBAL mapping type. * @return mixed The converted value. */ public function convertToDatabaseValue($value, $type) { return Type::getType($type)->convertToDatabaseValue($value, $this->_platform); } /** * Converts a given value to its PHP representation according to the conversion * rules of a specific DBAL mapping type. * * @param mixed $value The value to convert. * @param string $type The name of the DBAL mapping type. * @return mixed The converted type. */ public function convertToPHPValue($value, $type) { return Type::getType($type)->convertToPHPValue($value, $this->_platform); } /** * Binds a set of parameters, some or all of which are typed with a PDO binding type * or DBAL mapping type, to a given statement. * * @param $stmt The statement to bind the values to. * @param array $params The map/list of named/positional parameters. * @param array $types The parameter types (PDO binding types or DBAL mapping types). * @internal Duck-typing used on the $stmt parameter to support driver statements as well as * raw PDOStatement instances. */ private function _bindTypedValues($stmt, array $params, array $types) { // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. if (is_int(key($params))) { // Positional parameters $typeOffset = isset($types[0]) ? -1 : 0; $bindIndex = 1; foreach ($params as $position => $value) { $typeIndex = $bindIndex + $typeOffset; if (isset($types[$typeIndex])) { $type = $types[$typeIndex]; if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->_platform); $bindingType = $type->getBindingType(); } else { $bindingType = $type; // PDO::PARAM_* constants } $stmt->bindValue($bindIndex, $value, $bindingType); } else { $stmt->bindValue($bindIndex, $value); } ++$bindIndex; } } else { // Named parameters foreach ($params as $name => $value) { if (isset($types[$name])) { $type = $types[$name]; if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $value = $type->convertToDatabaseValue($value, $this->_platform); $bindingType = $type->getBindingType(); } else { $bindingType = $type; // PDO::PARAM_* constants } $stmt->bindValue($name, $value, $bindingType); } else { $stmt->bindValue($name, $value); } } } } }. */ namespace Doctrine\DBAL\Logging; /** * A SQL logger that logs to the standard output using echo/var_dump. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EchoSQLLogger implements SQLLogger { /** * {@inheritdoc} */ public function startQuery($sql, array $params = null, array $types = null) { echo $sql . PHP_EOL; if ($params) { var_dump($params); } if ($types) { var_dump($types); } } /** * {@inheritdoc} */ public function stopQuery() { } }. */ namespace Doctrine\DBAL\Logging; /** * Includes executed SQLs in a Debug Stack * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DebugStack implements SQLLogger { /** @var array $queries Executed SQL queries. */ public $queries = array(); /** @var boolean $enabled If Debug Stack is enabled (log queries) or not. */ public $enabled = true; public $start = null; /** * {@inheritdoc} */ public function startQuery($sql, array $params = null, array $types = null) { if ($this->enabled) { $this->start = microtime(true); $this->queries[] = array('sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0); } } /** * {@inheritdoc} */ public function stopQuery() { $this->queries[(count($this->queries)-1)]['executionMS'] = microtime(true) - $this->start; } } . */ namespace Doctrine\DBAL\Logging; /** * Interface for SQL loggers. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ interface SQLLogger { /** * Logs a SQL statement somewhere. * * @param string $sql The SQL to be executed. * @param array $params The SQL parameters. * @param float $executionMS The microtime difference it took to execute this query. * @return void */ public function startQuery($sql, array $params = null, array $types = null); /** * Mark the last started query as stopped. This can be used for timing of queries. * * @return void */ public function stopQuery(); }. */ namespace Doctrine\DBAL\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console; /** * Task for executing arbitrary SQL that can come from a file or directly from * the command line. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ImportCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('dbal:import') ->setDescription('Import SQL file(s) directly to Database.') ->setDefinition(array( new InputArgument( 'file', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'File path(s) of SQL to be executed.' ) )) ->setHelp(<<getHelper('db')->getConnection(); if (($fileNames = $input->getArgument('file')) !== null) { foreach ((array) $fileNames as $fileName) { $fileName = realpath($fileName); if ( ! file_exists($fileName)) { throw new \InvalidArgumentException( sprintf("SQL file '%s' does not exist.", $fileName) ); } else if ( ! is_readable($fileName)) { throw new \InvalidArgumentException( sprintf("SQL file '%s' does not have read permissions.", $fileName) ); } $output->write(sprintf("Processing file '%s'... ", $fileName)); $sql = file_get_contents($fileName); if ($conn instanceof \Doctrine\DBAL\Driver\PDOConnection) { // PDO Drivers try { $lines = 0; $stmt = $conn->prepare($sql); $stmt->execute(); do { // Required due to "MySQL has gone away!" issue $stmt->fetch(); $stmt->closeCursor(); $lines++; } while ($stmt->nextRowset()); $output->write(sprintf('%d statements executed!', $lines) . PHP_EOL); } catch (\PDOException $e) { $output->write('error!' . PHP_EOL); throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); } } else { // Non-PDO Drivers (ie. OCI8 driver) $stmt = $conn->prepare($sql); $rs = $stmt->execute(); if ($rs) { $printer->writeln('OK!'); } else { $error = $stmt->errorInfo(); $output->write('error!' . PHP_EOL); throw new \RuntimeException($error[2], $error[0]); } $stmt->closeCursor(); } } } } } . */ namespace Doctrine\DBAL\Tools\Console\Command; use Symfony\Component\Console\Input\InputArgument, Symfony\Component\Console\Input\InputOption, Symfony\Component\Console; /** * Task for executing arbitrary SQL that can come from a file or directly from * the command line. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class RunSqlCommand extends Console\Command\Command { /** * @see Console\Command\Command */ protected function configure() { $this ->setName('dbal:run-sql') ->setDescription('Executes arbitrary SQL directly from the command line.') ->setDefinition(array( new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), new InputOption('depth', null, InputOption::PARAMETER_REQUIRED, 'Dumping depth of result set.', 7) )) ->setHelp(<<getHelper('db')->getConnection(); if (($sql = $input->getArgument('sql')) === null) { throw new \RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); } $depth = $input->getOption('depth'); if ( ! is_numeric($depth)) { throw new \LogicException("Option 'depth' must contains an integer value"); } if (preg_match('/^select/i', $sql)) { $resultSet = $conn->fetchAll($sql); } else { $resultSet = $conn->executeUpdate($sql); } \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth); } } . */ namespace Doctrine\DBAL\Tools\Console\Helper; use Symfony\Component\Console\Helper\Helper, Doctrine\DBAL\Connection; /** * Doctrine CLI Connection Helper. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ConnectionHelper extends Helper { /** * Doctrine Database Connection * @var Connection */ protected $_connection; /** * Constructor * * @param Connection $connection Doctrine Database Connection */ public function __construct(Connection $connection) { $this->_connection = $connection; } /** * Retrieves Doctrine Database Connection * * @return Connection */ public function getConnection() { return $this->_connection; } /** * @see Helper */ public function getName() { return 'connection'; } } . */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Variable DateTime Type using date_create() instead of DateTime::createFromFormat() * * This type has performance implications as it runs twice as long as the regular * {@see DateTimeType}, however in certain PostgreSQL configurations with * TIMESTAMP(n) columns where n > 0 it is necessary to use this type. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class VarDateTimeType extends DateTimeType { /** * @throws ConversionException * @param string $value * @param AbstractPlatform $platform * @return DateTime */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $val = date_create($value); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL INT to a PHP integer. * * @author Roman Borschel * @since 2.0 */ class IntegerType extends Type { public function getName() { return Type::INTEGER; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration); } public function convertToPHPValue($value, AbstractPlatform $platform) { return (null === $value) ? null : (int) $value; } public function getBindingType() { return \PDO::PARAM_INT; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL DATE to a PHP Date object. * * @since 2.0 */ class DateType extends Type { public function getName() { return Type::DATE; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getDateTypeDeclarationSQL($fieldDeclaration); } public function convertToDatabaseValue($value, AbstractPlatform $platform) { return ($value !== null) ? $value->format($platform->getDateFormatString()) : null; } public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $val = \DateTime::createFromFormat('!'.$platform->getDateFormatString(), $value); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform, Doctrine\DBAL\DBALException; /** * The base class for so-called Doctrine mapping types. * * A Type object is obtained by calling the static {@link getType()} method. * * @author Roman Borschel * @since 2.0 */ abstract class Type { const TARRAY = 'array'; const BIGINT = 'bigint'; const BOOLEAN = 'boolean'; const DATETIME = 'datetime'; const DATETIMETZ = 'datetimetz'; const DATE = 'date'; const TIME = 'time'; const DECIMAL = 'decimal'; const INTEGER = 'integer'; const OBJECT = 'object'; const SMALLINT = 'smallint'; const STRING = 'string'; const TEXT = 'text'; const FLOAT = 'float'; /** Map of already instantiated type objects. One instance per type (flyweight). */ private static $_typeObjects = array(); /** The map of supported doctrine mapping types. */ private static $_typesMap = array( self::TARRAY => 'Doctrine\DBAL\Types\ArrayType', self::OBJECT => 'Doctrine\DBAL\Types\ObjectType', self::BOOLEAN => 'Doctrine\DBAL\Types\BooleanType', self::INTEGER => 'Doctrine\DBAL\Types\IntegerType', self::SMALLINT => 'Doctrine\DBAL\Types\SmallIntType', self::BIGINT => 'Doctrine\DBAL\Types\BigIntType', self::STRING => 'Doctrine\DBAL\Types\StringType', self::TEXT => 'Doctrine\DBAL\Types\TextType', self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType', self::DATETIMETZ => 'Doctrine\DBAL\Types\DateTimeTzType', self::DATE => 'Doctrine\DBAL\Types\DateType', self::TIME => 'Doctrine\DBAL\Types\TimeType', self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType', self::FLOAT => 'Doctrine\DBAL\Types\FloatType', ); /* Prevent instantiation and force use of the factory method. */ final private function __construct() {} /** * Converts a value from its PHP representation to its database representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * @return mixed The database representation of the value. */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $value; } /** * Converts a value from its database representation to its PHP representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * @return mixed The PHP representation of the value. */ public function convertToPHPValue($value, AbstractPlatform $platform) { return $value; } /** * Gets the default length of this type. * * @todo Needed? */ public function getDefaultLength(AbstractPlatform $platform) { return null; } /** * Gets the SQL declaration snippet for a field of this type. * * @param array $fieldDeclaration The field declaration. * @param AbstractPlatform $platform The currently used database platform. */ abstract public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform); /** * Gets the name of this type. * * @return string * @todo Needed? */ abstract public function getName(); /** * Factory method to create type instances. * Type instances are implemented as flyweights. * * @static * @throws DBALException * @param string $name The name of the type (as returned by getName()). * @return Doctrine\DBAL\Types\Type */ public static function getType($name) { if ( ! isset(self::$_typeObjects[$name])) { if ( ! isset(self::$_typesMap[$name])) { throw DBALException::unknownColumnType($name); } self::$_typeObjects[$name] = new self::$_typesMap[$name](); } return self::$_typeObjects[$name]; } /** * Adds a custom type to the type map. * * @static * @param string $name Name of the type. This should correspond to what getName() returns. * @param string $className The class name of the custom type. * @throws DBALException */ public static function addType($name, $className) { if (isset(self::$_typesMap[$name])) { throw DBALException::typeExists($name); } self::$_typesMap[$name] = $className; } /** * Checks if exists support for a type. * * @static * @param string $name Name of the type * @return boolean TRUE if type is supported; FALSE otherwise */ public static function hasType($name) { return isset(self::$_typesMap[$name]); } /** * Overrides an already defined type to use a different implementation. * * @static * @param string $name * @param string $className * @throws DBALException */ public static function overrideType($name, $className) { if ( ! isset(self::$_typesMap[$name])) { throw DBALException::typeNotFound($name); } self::$_typesMap[$name] = $className; } /** * Gets the (preferred) binding type for values of this type that * can be used when binding parameters to prepared statements. * * This method should return one of the PDO::PARAM_* constants, that is, one of: * * PDO::PARAM_BOOL * PDO::PARAM_NULL * PDO::PARAM_INT * PDO::PARAM_STR * PDO::PARAM_LOB * * @return integer */ public function getBindingType() { return \PDO::PARAM_STR; } /** * Get the types array map which holds all registered types and the corresponding * type class * * @return array $typesMap */ public static function getTypesMap() { return self::$_typesMap; } public function __toString() { $e = explode('\\', get_class($this)); return str_replace('Type', '', end($e)); } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; class FloatType extends Type { public function getName() { return Type::FLOAT; } /** * @param array $fieldDeclaration * @param AbstractPlatform $platform * @return string */ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getFloatDeclarationSQL($fieldDeclaration); } /** * Converts a value from its database representation to its PHP representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * @return mixed The PHP representation of the value. */ public function convertToPHPValue($value, AbstractPlatform $platform) { return (null === $value) ? null : (double) $value; } } . */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database SMALLINT to a PHP integer. * * @author robo */ class SmallIntType extends Type { public function getName() { return Type::SMALLINT; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration); } public function convertToPHPValue($value, AbstractPlatform $platform) { return (null === $value) ? null : (int) $value; } public function getBindingType() { return \PDO::PARAM_INT; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps a database BIGINT to a PHP string. * * @author robo * @since 2.0 */ class BigIntType extends Type { public function getName() { return Type::BIGINT; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getBigIntTypeDeclarationSQL($fieldDeclaration); } public function getBindingType() { return \PDO::PARAM_INT; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL VARCHAR to a PHP string. * * @since 2.0 */ class StringType extends Type { /** @override */ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration); } /** @override */ public function getDefaultLength(AbstractPlatform $platform) { return $platform->getVarcharDefaultLength(); } /** @override */ public function getName() { return Type::STRING; } }getClobTypeDeclarationSQL($fieldDeclaration); } public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { return serialize($value); } public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { if ($value === null) { return null; } $value = (is_resource($value)) ? stream_get_contents($value) : $value; $val = unserialize($value); if ($val === false) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } public function getName() { return Type::OBJECT; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * DateTime type saving additional timezone information. * * Caution: Databases are not necessarily experts at storing timezone related * data of dates. First, of all the supported vendors only PostgreSQL and Oracle * support storing Timezone data. But those two don't save the actual timezone * attached to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") * but the current offset of them related to UTC. That means depending on daylight saving times * or not you may get different offsets. * * This datatype makes only sense to use, if your application works with an offset, not * with an actual timezone that uses transitions. Otherwise your DateTime instance * attached with a timezone such as Europe/Berlin gets saved into the database with * the offset and re-created from persistence with only the offset, not the original timezone * attached. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class DateTimeTzType extends Type { public function getName() { return Type::DATETIMETZ; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getDateTimeTzTypeDeclarationSQL($fieldDeclaration); } public function convertToDatabaseValue($value, AbstractPlatform $platform) { return ($value !== null) ? $value->format($platform->getDateTimeTzFormatString()) : null; } public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $val = \DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } }. */ /** * Conversion Exception is thrown when the database to PHP conversion fails * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ namespace Doctrine\DBAL\Types; class ConversionException extends \Doctrine\DBAL\DBALException { /** * Thrown when a Database to Doctrine Type Conversion fails. * * @param string $value * @param string $toType * @return ConversionException */ static public function conversionFailed($value, $toType) { $value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value; return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType); } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. * * @since 2.0 */ class DateTimeType extends Type { public function getName() { return Type::DATETIME; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getDateTimeTypeDeclarationSQL($fieldDeclaration); } public function convertToDatabaseValue($value, AbstractPlatform $platform) { return ($value !== null) ? $value->format($platform->getDateTimeFormatString()) : null; } public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $val = \DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL DECIMAL to a PHP double. * * @since 2.0 */ class DecimalType extends Type { public function getName() { return Type::DECIMAL; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); } public function convertToPHPValue($value, AbstractPlatform $platform) { return (null === $value) ? null : (double) $value; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL CLOB to a PHP string. * * @since 2.0 */ class TextType extends Type { /** @override */ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($fieldDeclaration); } /** * Converts a value from its database representation to its PHP representation * of this type. * * @param mixed $value The value to convert. * @param AbstractPlatform $platform The currently used database platform. * @return mixed The PHP representation of the value. */ public function convertToPHPValue($value, AbstractPlatform $platform) { return (is_resource($value)) ? stream_get_contents($value) : $value; } public function getName() { return Type::TEXT; } }. */ namespace Doctrine\DBAL\Types; /** * Type that maps a PHP array to a clob SQL type. * * @since 2.0 */ class ArrayType extends Type { public function getSQLDeclaration(array $fieldDeclaration, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { return $platform->getClobTypeDeclarationSQL($fieldDeclaration); } public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { return serialize($value); } public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform) { if ($value === null) { return null; } $value = (is_resource($value)) ? stream_get_contents($value) : $value; $val = unserialize($value); if ($val === false) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } public function getName() { return Type::TARRAY; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL boolean to a PHP boolean. * * @since 2.0 */ class BooleanType extends Type { public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getBooleanTypeDeclarationSQL($fieldDeclaration); } public function convertToDatabaseValue($value, AbstractPlatform $platform) { return $platform->convertBooleans($value); } public function convertToPHPValue($value, AbstractPlatform $platform) { return (null === $value) ? null : (bool) $value; } public function getName() { return Type::BOOLEAN; } public function getBindingType() { return \PDO::PARAM_BOOL; } }. */ namespace Doctrine\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; /** * Type that maps an SQL TIME to a PHP DateTime object. * * @since 2.0 */ class TimeType extends Type { public function getName() { return Type::TIME; } /** * {@inheritdoc} */ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { return $platform->getTimeTypeDeclarationSQL($fieldDeclaration); } /** * {@inheritdoc} */ public function convertToDatabaseValue($value, AbstractPlatform $platform) { return ($value !== null) ? $value->format($platform->getTimeFormatString()) : null; } /** * {@inheritdoc} */ public function convertToPHPValue($value, AbstractPlatform $platform) { if ($value === null) { return null; } $val = \DateTime::createFromFormat($platform->getTimeFormatString(), $value); if (!$val) { throw ConversionException::conversionFailed($value, $this->getName()); } return $val; } }. */ namespace Doctrine\DBAL\Event; use Doctrine\Common\EventArgs, Doctrine\DBAL\Connection; /** * Event Arguments used when a Driver connection is established inside Doctrine\DBAL\Connection. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei */ class ConnectionEventArgs extends EventArgs { /** * @var Connection */ private $_connection = null; public function __construct(Connection $connection) { $this->_connection = $connection; } /** * @return Doctrine\DBAL\Connection */ public function getConnection() { return $this->_connection; } /** * @return Doctrine\DBAL\Driver */ public function getDriver() { return $this->_connection->getDriver(); } /** * @return Doctrine\DBAL\Platforms\AbstractPlatform */ public function getDatabasePlatform() { return $this->_connection->getDatabasePlatform(); } /** * @return Doctrine\DBAL\Schema\AbstractSchemaManager */ public function getSchemaManager() { return $this->_connection->getSchemaManager(); } } . */ namespace Doctrine\DBAL\Event\Listeners; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\Common\EventSubscriber; /** * MySQL Session Init Event Subscriber which allows to set the Client Encoding of the Connection * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei */ class MysqlSessionInit implements EventSubscriber { /** * @var string */ private $_charset; /** * @var string */ private $_collation; /** * Configure Charset and Collation options of MySQL Client for each Connection * * @param string $charset * @param string $collation */ public function __construct($charset = 'utf8', $collation = false) { $this->_charset = $charset; $this->_collation = $collation; } /** * @param ConnectionEventArgs $args * @return void */ public function postConnect(ConnectionEventArgs $args) { $collation = ($this->_collation) ? " COLLATE ".$this->_collation : ""; $args->getConnection()->executeUpdate("SET NAMES ".$this->_charset . $collation); } public function getSubscribedEvents() { return array(Events::postConnect); } }. */ namespace Doctrine\DBAL\Event\Listeners; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; use Doctrine\Common\EventSubscriber; /** * Should be used when Oracle Server default enviroment does not match the Doctrine requirements. * * The following enviroment variables are required for the Doctrine default date format: * * NLS_TIME_FORMAT="HH24:MI:SS" * NLS_DATE_FORMAT="YYYY-MM-DD" * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS" * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM" * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com * @since 1.0 * @version $Revision$ * @author Benjamin Eberlei */ class OracleSessionInit implements EventSubscriber { protected $_defaultSessionVars = array( 'NLS_TIME_FORMAT' => "HH24:MI:SS", 'NLS_DATE_FORMAT' => "YYYY-MM-DD HH24:MI:SS", 'NLS_TIMESTAMP_FORMAT' => "YYYY-MM-DD HH24:MI:SS", 'NLS_TIMESTAMP_TZ_FORMAT' => "YYYY-MM-DD HH24:MI:SS TZH:TZM", ); /** * @param array $oracleSessionVars */ public function __construct(array $oracleSessionVars = array()) { $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); } /** * @param ConnectionEventArgs $args * @return void */ public function postConnect(ConnectionEventArgs $args) { if (count($this->_defaultSessionVars)) { array_change_key_case($this->_defaultSessionVars, \CASE_UPPER); $vars = array(); foreach ($this->_defaultSessionVars AS $option => $value) { $vars[] = $option." = '".$value."'"; } $sql = "ALTER SESSION SET ".implode(" ", $vars); $args->getConnection()->executeUpdate($sql); } } public function getSubscribedEvents() { return array(Events::postConnect); } } . */ namespace Doctrine\Common; /** * EventArgs is the base class for classes containing event data. * * This class contains no event data. It is used by events that do not pass state * information to an event handler when an event is raised. The single empty EventArgs * instance can be obtained through {@link getEmptyInstance}. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EventArgs { /** * @var EventArgs Single instance of EventArgs * @static */ private static $_emptyEventArgsInstance; /** * Gets the single, empty and immutable EventArgs instance. * * This instance will be used when events are dispatched without any parameter, * like this: EventManager::dispatchEvent('eventname'); * * The benefit from this is that only one empty instance is instantiated and shared * (otherwise there would be instances for every dispatched in the abovementioned form) * * @see EventManager::dispatchEvent * @link http://msdn.microsoft.com/en-us/library/system.eventargs.aspx * @static * @return EventArgs */ public static function getEmptyInstance() { if ( ! self::$_emptyEventArgsInstance) { self::$_emptyEventArgsInstance = new EventArgs; } return self::$_emptyEventArgsInstance; } } . */ namespace Doctrine\Common; /** * Base exception class for package Doctrine\Common * @author heinrich * */ class CommonException extends \Exception { }. */ namespace Doctrine\Common\Annotations; use Closure, ReflectionClass, ReflectionMethod, ReflectionProperty, Doctrine\Common\Cache\Cache; /** * A reader for docblock annotations. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class AnnotationReader { /** * Cache salt * * @var string * @static */ private static $CACHE_SALT = '@'; /** * Annotations Parser * * @var Doctrine\Common\Annotations\Parser */ private $parser; /** * Cache mechanism to store processed Annotations * * @var Doctrine\Common\Cache\Cache */ private $cache; /** * Constructor. Initializes a new AnnotationReader that uses the given Cache provider. * * @param Cache $cache The cache provider to use. If none is provided, ArrayCache is used. * @param Parser $parser The parser to use. If none is provided, the default parser is used. */ public function __construct(Cache $cache = null, Parser $parser = null) { $this->parser = $parser ?: new Parser; $this->cache = $cache ?: new \Doctrine\Common\Cache\ArrayCache; } /** * Sets the default namespace that the AnnotationReader should assume for annotations * with not fully qualified names. * * @param string $defaultNamespace */ public function setDefaultAnnotationNamespace($defaultNamespace) { $this->parser->setDefaultAnnotationNamespace($defaultNamespace); } /** * Sets the custom function to use for creating new annotations on the * underlying parser. * * The function is supplied two arguments. The first argument is the name * of the annotation and the second argument an array of values for this * annotation. The function is assumed to return an object or NULL. * Whenever the function returns NULL for an annotation, the implementation falls * back to the default annotation creation process of the underlying parser. * * @param Closure $func */ public function setAnnotationCreationFunction(Closure $func) { $this->parser->setAnnotationCreationFunction($func); } /** * Sets an alias for an annotation namespace. * * @param $namespace * @param $alias */ public function setAnnotationNamespaceAlias($namespace, $alias) { $this->parser->setAnnotationNamespaceAlias($namespace, $alias); } /** * Sets a flag whether to try to autoload annotation classes, as well as to distinguish * between what is an annotation and what not by triggering autoloading. * * NOTE: Autoloading of annotation classes is inefficient and requires silently failing * autoloaders. In particular, setting this option to TRUE renders this AnnotationReader * incompatible with a {@link ClassLoader}. * @param boolean $bool Boolean flag. */ public function setAutoloadAnnotations($bool) { $this->parser->setAutoloadAnnotations($bool); } /** * Gets a flag whether to try to autoload annotation classes. * * @see setAutoloadAnnotations * @return boolean */ public function getAutoloadAnnotations() { return $this->parser->getAutoloadAnnotations(); } /** * Gets the annotations applied to a class. * * @param string|ReflectionClass $class The name or ReflectionClass of the class from which * the class annotations should be read. * @return array An array of Annotations. */ public function getClassAnnotations(ReflectionClass $class) { $cacheKey = $class->getName() . self::$CACHE_SALT; // Attempt to grab data from cache if (($data = $this->cache->fetch($cacheKey)) !== false) { return $data; } $annotations = $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); $this->cache->save($cacheKey, $annotations, null); return $annotations; } /** * Gets a class annotation. * * @param $class * @param string $annotation The name of the annotation. * @return The Annotation or NULL, if the requested annotation does not exist. */ public function getClassAnnotation(ReflectionClass $class, $annotation) { $annotations = $this->getClassAnnotations($class); return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } /** * Gets the annotations applied to a property. * * @param string|ReflectionClass $class The name or ReflectionClass of the class that owns the property. * @param string|ReflectionProperty $property The name or ReflectionProperty of the property * from which the annotations should be read. * @return array An array of Annotations. */ public function getPropertyAnnotations(ReflectionProperty $property) { $cacheKey = $property->getDeclaringClass()->getName() . '$' . $property->getName() . self::$CACHE_SALT; // Attempt to grab data from cache if (($data = $this->cache->fetch($cacheKey)) !== false) { return $data; } $context = 'property ' . $property->getDeclaringClass()->getName() . "::\$" . $property->getName(); $annotations = $this->parser->parse($property->getDocComment(), $context); $this->cache->save($cacheKey, $annotations, null); return $annotations; } /** * Gets a property annotation. * * @param ReflectionProperty $property * @param string $annotation The name of the annotation. * @return The Annotation or NULL, if the requested annotation does not exist. */ public function getPropertyAnnotation(ReflectionProperty $property, $annotation) { $annotations = $this->getPropertyAnnotations($property); return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } /** * Gets the annotations applied to a method. * * @param string|ReflectionClass $class The name or ReflectionClass of the class that owns the method. * @param string|ReflectionMethod $property The name or ReflectionMethod of the method from which * the annotations should be read. * @return array An array of Annotations. */ public function getMethodAnnotations(ReflectionMethod $method) { $cacheKey = $method->getDeclaringClass()->getName() . '#' . $method->getName() . self::$CACHE_SALT; // Attempt to grab data from cache if (($data = $this->cache->fetch($cacheKey)) !== false) { return $data; } $context = 'method ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()'; $annotations = $this->parser->parse($method->getDocComment(), $context); $this->cache->save($cacheKey, $annotations, null); return $annotations; } /** * Gets a method annotation. * * @param ReflectionMethod $method * @param string $annotation The name of the annotation. * @return The Annotation or NULL, if the requested annotation does not exist. */ public function getMethodAnnotation(ReflectionMethod $method, $annotation) { $annotations = $this->getMethodAnnotations($method); return isset($annotations[$annotation]) ? $annotations[$annotation] : null; } }. */ namespace Doctrine\Common\Annotations; /** * Description of AnnotationException * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class AnnotationException extends \Exception { /** * Creates a new AnnotationException describing a Syntax error. * * @param string $message Exception message * @return AnnotationException */ public static function syntaxError($message) { return new self('[Syntax Error] ' . $message); } /** * Creates a new AnnotationException describing a Semantical error. * * @param string $message Exception message * @return AnnotationException */ public static function semanticalError($message) { return new self('[Semantical Error] ' . $message); } }. */ namespace Doctrine\Common\Annotations; use Closure, Doctrine\Common\ClassLoader; /** * A simple parser for docblock annotations. * * This Parser can be subclassed to customize certain aspects of the annotation * parsing and/or creation process. Note though that currently no special care * is taken to maintain full backwards compatibility for subclasses. Implementation * details of the default Parser can change without explicit notice. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Parser { /** * Some common tags that are stripped prior to parsing in order to reduce parsing overhead. * * @var array */ private static $strippedTags = array( "{@internal", "{@inheritdoc", "{@link" ); /** * The lexer. * * @var Doctrine\Common\Annotations\Lexer */ private $lexer; /** * Flag to control if the current annotation is nested or not. * * @var boolean */ protected $isNestedAnnotation = false; /** * Default namespace for annotations. * * @var string */ private $defaultAnnotationNamespace = ''; /** * Hashmap to store namespace aliases. * * @var array */ private $namespaceAliases = array(); /** * @var string */ private $context = ''; /** * @var boolean Whether to try to autoload annotations that are not yet defined. */ private $autoloadAnnotations = false; /** * @var Closure The custom function used to create new annotations, if any. */ private $annotationCreationFunction; /** * Constructs a new AnnotationParser. */ public function __construct(Lexer $lexer = null) { $this->lexer = $lexer ?: new Lexer; } /** * Gets the lexer used by this parser. * * @return Lexer The lexer. */ public function getLexer() { return $this->lexer; } /** * Sets a flag whether to try to autoload annotation classes, as well as to distinguish * between what is an annotation and what not by triggering autoloading. * * NOTE: Autoloading of annotation classes is inefficient and requires silently failing * autoloaders. In particular, setting this option to TRUE renders the Parser * incompatible with a {@link ClassLoader}. * @param boolean $bool Boolean flag. */ public function setAutoloadAnnotations($bool) { $this->autoloadAnnotations = $bool; } /** * Sets the custom function to use for creating new annotations. * * The function is supplied two arguments. The first argument is the name * of the annotation and the second argument an array of values for this * annotation. The function is assumed to return an object or NULL. * Whenever the function returns NULL for an annotation, the parser falls * back to the default annotation creation process. * * Whenever the function returns NULL for an annotation, the implementation falls * back to the default annotation creation process. * * @param Closure $func */ public function setAnnotationCreationFunction(Closure $func) { $this->annotationCreationFunction = $func; } /** * Gets a flag whether to try to autoload annotation classes. * * @see setAutoloadAnnotations * @return boolean */ public function getAutoloadAnnotations() { return $this->autoloadAnnotations; } /** * Sets the default namespace that is assumed for an annotation that does not * define a namespace prefix. * * @param string $defaultNamespace */ public function setDefaultAnnotationNamespace($defaultNamespace) { $this->defaultAnnotationNamespace = $defaultNamespace; } /** * Sets an alias for an annotation namespace. * * @param string $namespace * @param string $alias */ public function setAnnotationNamespaceAlias($namespace, $alias) { $this->namespaceAliases[$alias] = $namespace; } /** * Gets the namespace alias mappings used by this parser. * * @return array The namespace alias mappings. */ public function getNamespaceAliases() { return $this->namespaceAliases; } /** * Parses the given docblock string for annotations. * * @param string $docBlockString The docblock string to parse. * @param string $context The parsing context. * @return array Array of annotations. If no annotations are found, an empty array is returned. */ public function parse($docBlockString, $context='') { $this->context = $context; // Strip out some known inline tags. $input = str_replace(self::$strippedTags, '', $docBlockString); // Cut of the beginning of the input until the first '@'. $input = substr($input, strpos($input, '@')); $this->lexer->reset(); $this->lexer->setInput(trim($input, '* /')); $this->lexer->moveNext(); if ($this->lexer->isNextToken(Lexer::T_AT)) { return $this->Annotations(); } return array(); } /** * Attempts to match the given token with the current lookahead token. * If they match, updates the lookahead token; otherwise raises a syntax error. * * @param int Token type. * @return bool True if tokens match; false otherwise. */ public function match($token) { if ( ! ($this->lexer->lookahead['type'] === $token)) { $this->syntaxError($this->lexer->getLiteral($token)); } $this->lexer->moveNext(); } /** * Generates a new syntax error. * * @param string $expected Expected string. * @param array $token Optional token. * @throws AnnotationException */ private function syntaxError($expected, $token = null) { if ($token === null) { $token = $this->lexer->lookahead; } $message = "Expected {$expected}, got "; if ($this->lexer->lookahead === null) { $message .= 'end of string'; } else { $message .= "'{$token['value']}' at position {$token['position']}"; } if (strlen($this->context)) { $message .= ' in ' . $this->context; } $message .= '.'; throw AnnotationException::syntaxError($message); } /** * Annotations ::= Annotation {[ "*" ]* [Annotation]}* * * @return array */ public function Annotations() { $this->isNestedAnnotation = false; $annotations = array(); $annot = $this->Annotation(); if ($annot !== false) { $annotations[get_class($annot)] = $annot; $this->lexer->skipUntil(Lexer::T_AT); } while ($this->lexer->lookahead !== null && $this->lexer->isNextToken(Lexer::T_AT)) { $this->isNestedAnnotation = false; $annot = $this->Annotation(); if ($annot !== false) { $annotations[get_class($annot)] = $annot; $this->lexer->skipUntil(Lexer::T_AT); } } return $annotations; } /** * Annotation ::= "@" AnnotationName ["(" [Values] ")"] * AnnotationName ::= QualifiedName | SimpleName | AliasedName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * AliasedName ::= Alias ":" SimpleName * NameSpacePart ::= identifier * SimpleName ::= identifier * Alias ::= identifier * * @return mixed False if it is not a valid annotation. */ public function Annotation() { $values = array(); $nameParts = array(); $this->match(Lexer::T_AT); $this->match(Lexer::T_IDENTIFIER); $nameParts[] = $this->lexer->token['value']; while ($this->lexer->isNextToken(Lexer::T_NAMESPACE_SEPARATOR)) { $this->match(Lexer::T_NAMESPACE_SEPARATOR); $this->match(Lexer::T_IDENTIFIER); $nameParts[] = $this->lexer->token['value']; } // Effectively pick the name of the class (append default NS if none, grab from NS alias, etc) if (strpos($nameParts[0], ':')) { list ($alias, $nameParts[0]) = explode(':', $nameParts[0]); // If the namespace alias doesnt exist, skip until next annotation if ( ! isset($this->namespaceAliases[$alias])) { $this->lexer->skipUntil(Lexer::T_AT); return false; } $name = $this->namespaceAliases[$alias] . implode('\\', $nameParts); } else if (count($nameParts) == 1) { $name = $this->defaultAnnotationNamespace . $nameParts[0]; } else { $name = implode('\\', $nameParts); } // Does the annotation class exist? if ( ! class_exists($name, $this->autoloadAnnotations)) { $this->lexer->skipUntil(Lexer::T_AT); return false; } // Next will be nested $this->isNestedAnnotation = true; if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { $this->match(Lexer::T_OPEN_PARENTHESIS); if ( ! $this->lexer->isNextToken(Lexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(Lexer::T_CLOSE_PARENTHESIS); } if ($this->annotationCreationFunction !== null) { $func = $this->annotationCreationFunction; $annot = $func($name, $values); } return isset($annot) ? $annot : $this->newAnnotation($name, $values); } /** * Values ::= Array | Value {"," Value}* * * @return array */ public function Values() { $values = array(); // Handle the case of a single array as value, i.e. @Foo({....}) if ($this->lexer->isNextToken(Lexer::T_OPEN_CURLY_BRACES)) { $values['value'] = $this->Value(); return $values; } $values[] = $this->Value(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $value = $this->Value(); if ( ! is_array($value)) { $this->syntaxError('Value', $value); } $values[] = $value; } foreach ($values as $k => $value) { if (is_array($value) && is_string(key($value))) { $key = key($value); $values[$key] = $value[$key]; } else { $values['value'] = $value; } unset($values[$k]); } return $values; } /** * Value ::= PlainValue | FieldAssignment * * @return mixed */ public function Value() { $peek = $this->lexer->glimpse(); if ($peek['value'] === '=') { return $this->FieldAssignment(); } return $this->PlainValue(); } /** * PlainValue ::= integer | string | float | boolean | Array | Annotation * * @return mixed */ public function PlainValue() { if ($this->lexer->isNextToken(Lexer::T_OPEN_CURLY_BRACES)) { return $this->Arrayx(); } if ($this->lexer->isNextToken(Lexer::T_AT)) { return $this->Annotation(); } switch ($this->lexer->lookahead['type']) { case Lexer::T_STRING: $this->match(Lexer::T_STRING); return $this->lexer->token['value']; case Lexer::T_INTEGER: $this->match(Lexer::T_INTEGER); return $this->lexer->token['value']; case Lexer::T_FLOAT: $this->match(Lexer::T_FLOAT); return $this->lexer->token['value']; case Lexer::T_TRUE: $this->match(Lexer::T_TRUE); return true; case Lexer::T_FALSE: $this->match(Lexer::T_FALSE); return false; default: $this->syntaxError('PlainValue'); } } /** * FieldAssignment ::= FieldName "=" PlainValue * FieldName ::= identifier * * @return array */ public function FieldAssignment() { $this->match(Lexer::T_IDENTIFIER); $fieldName = $this->lexer->token['value']; $this->match(Lexer::T_EQUALS); return array($fieldName => $this->PlainValue()); } /** * Array ::= "{" ArrayEntry {"," ArrayEntry}* "}" * * @return array */ public function Arrayx() { $array = $values = array(); $this->match(Lexer::T_OPEN_CURLY_BRACES); $values[] = $this->ArrayEntry(); while ($this->lexer->isNextToken(Lexer::T_COMMA)) { $this->match(Lexer::T_COMMA); $values[] = $this->ArrayEntry(); } $this->match(Lexer::T_CLOSE_CURLY_BRACES); foreach ($values as $value) { list ($key, $val) = $value; if ($key !== null) { $array[$key] = $val; } else { $array[] = $val; } } return $array; } /** * ArrayEntry ::= Value | KeyValuePair * KeyValuePair ::= Key "=" PlainValue * Key ::= string | integer * * @return array */ public function ArrayEntry() { $peek = $this->lexer->glimpse(); if ($peek['value'] == '=') { $this->match( $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_STRING ); $key = $this->lexer->token['value']; $this->match(Lexer::T_EQUALS); return array($key, $this->PlainValue()); } return array(null, $this->Value()); } /** * Constructs a new annotation with a given map of values. * * The default construction procedure is to instantiate a new object of a class * with the same name as the annotation. Subclasses can override this method to * change the construction process of new annotations. * * @param string The name of the annotation. * @param array The map of annotation values. * @return mixed The new annotation with the given values. */ protected function newAnnotation($name, array $values) { return new $name($values); } }. */ namespace Doctrine\Common\Annotations; /** * Simple lexer for docblock annotations. * * This Lexer can be subclassed to customize certain aspects of the annotation * lexing (token recognition) process. Note though that currently no special care * is taken to maintain full backwards compatibility for subclasses. Implementation * details of the default Lexer can change without explicit notice. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Lexer extends \Doctrine\Common\Lexer { const T_NONE = 1; const T_IDENTIFIER = 2; const T_INTEGER = 3; const T_STRING = 4; const T_FLOAT = 5; const T_AT = 101; const T_CLOSE_CURLY_BRACES = 102; const T_CLOSE_PARENTHESIS = 103; const T_COMMA = 104; const T_EQUALS = 105; const T_FALSE = 106; const T_NAMESPACE_SEPARATOR = 107; const T_OPEN_CURLY_BRACES = 108; const T_OPEN_PARENTHESIS = 109; const T_TRUE = 110; /** * @inheritdoc */ protected function getCatchablePatterns() { return array( '[a-z_][a-z0-9_:]*', '(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', '"(?:[^"]|"")*"' ); } /** * @inheritdoc */ protected function getNonCatchablePatterns() { return array('\s+', '\*+', '(.)'); } /** * @inheritdoc */ protected function getType(&$value) { $type = self::T_NONE; $newVal = $this->getNumeric($value); // Checking numeric value if ($newVal !== false) { $value = $newVal; return (strpos($value, '.') !== false || stripos($value, 'e') !== false) ? self::T_FLOAT : self::T_INTEGER; } if ($value[0] === '"') { $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); return self::T_STRING; } else { switch (strtolower($value)) { case '@': return self::T_AT; case ',': return self::T_COMMA; case '(': return self::T_OPEN_PARENTHESIS; case ')': return self::T_CLOSE_PARENTHESIS; case '{': return self::T_OPEN_CURLY_BRACES; case '}': return self::T_CLOSE_CURLY_BRACES; case '=': return self::T_EQUALS; case '\\': return self::T_NAMESPACE_SEPARATOR; case 'true': return self::T_TRUE; case 'false': return self::T_FALSE; default: if (ctype_alpha($value[0]) || $value[0] === '_') { return self::T_IDENTIFIER; } break; } } return $type; } /** * Checks if a value is numeric or not * * @param mixed $value Value to be inspected * @return boolean|integer|float Processed value * @todo Inline */ private function getNumeric($value) { if ( ! is_scalar($value)) { return false; } // Checking for valid numeric numbers: 1.234, -1.234e-2 if (is_numeric($value)) { return $value; } return false; } }. */ namespace Doctrine\Common\Annotations; /** * Annotations class * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Annotation { /** * Value property. Common among all derived classes. * * @var string */ public $value; /** * Constructor * * @param array $data Key-value for properties to be defined in this class */ public final function __construct(array $data) { foreach ($data as $key => $value) { $this->$key = $value; } } /** * Error handler for unknown property accessor in Annotation class. * * @param string $name Unknown property name */ public function __get($name) { throw new \BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) ); } /** * Error handler for unknown property mutator in Annotation class. * * @param string $name Unkown property name * @param mixed $value Property value */ public function __set($name, $value) { throw new \BadMethodCallException( sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) ); } }. */ namespace Doctrine\Common; /** * A ClassLoader is an autoloader for class files that can be * installed on the SPL autoload stack. It is a class loader that either loads only classes * of a specific namespace or all namespaces and it is suitable for working together * with other autoloaders in the SPL autoload stack. * * If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader * relies on the PHP include_path. * * @author Roman Borschel * @since 2.0 */ class ClassLoader { private $fileExtension = '.php'; private $namespace; private $includePath; private $namespaceSeparator = '\\'; /** * Creates a new ClassLoader that loads classes of the * specified namespace from the specified include path. * * If no include path is given, the ClassLoader relies on the PHP include_path. * If neither a namespace nor an include path is given, the ClassLoader will * be responsible for loading all classes, thereby relying on the PHP include_path. * * @param string $ns The namespace of the classes to load. * @param string $includePath The base include path to use. */ public function __construct($ns = null, $includePath = null) { $this->namespace = $ns; $this->includePath = $includePath; } /** * Sets the namespace separator used by classes in the namespace of this ClassLoader. * * @param string $sep The separator to use. */ public function setNamespaceSeparator($sep) { $this->namespaceSeparator = $sep; } /** * Gets the namespace separator used by classes in the namespace of this ClassLoader. * * @return string */ public function getNamespaceSeparator() { return $this->namespaceSeparator; } /** * Sets the base include path for all class files in the namespace of this ClassLoader. * * @param string $includePath */ public function setIncludePath($includePath) { $this->includePath = $includePath; } /** * Gets the base include path for all class files in the namespace of this ClassLoader. * * @return string */ public function getIncludePath() { return $this->includePath; } /** * Sets the file extension of class files in the namespace of this ClassLoader. * * @param string $fileExtension */ public function setFileExtension($fileExtension) { $this->fileExtension = $fileExtension; } /** * Gets the file extension of class files in the namespace of this ClassLoader. * * @return string */ public function getFileExtension() { return $this->fileExtension; } /** * Registers this ClassLoader on the SPL autoload stack. */ public function register() { spl_autoload_register(array($this, 'loadClass')); } /** * Removes this ClassLoader from the SPL autoload stack. */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); } /** * Loads the given class or interface. * * @param string $classname The name of the class to load. * @return boolean TRUE if the class has been successfully loaded, FALSE otherwise. */ public function loadClass($className) { if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) { return false; } require ($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '') . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension; return true; } /** * Asks this ClassLoader whether it can potentially load the class (file) with * the given name. * * @param string $className The fully-qualified name of the class. * @return boolean TRUE if this ClassLoader can load the class, FALSE otherwise. */ public function canLoadClass($className) { if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) { return false; } return file_exists(($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '') . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension); } /** * Checks whether a class with a given name exists. A class "exists" if it is either * already defined in the current request or if there is an autoloader on the SPL * autoload stack that is a) responsible for the class in question and b) is able to * load a class file in which the class definition resides. * * If the class is not already defined, each autoloader in the SPL autoload stack * is asked whether it is able to tell if the class exists. If the autoloader is * a ClassLoader, {@link canLoadClass} is used, otherwise the autoload * function of the autoloader is invoked and expected to return a value that * evaluates to TRUE if the class (file) exists. As soon as one autoloader reports * that the class exists, TRUE is returned. * * Note that, depending on what kinds of autoloaders are installed on the SPL * autoload stack, the class (file) might already be loaded as a result of checking * for its existence. This is not the case with a ClassLoader, who separates * these responsibilities. * * @param string $className The fully-qualified name of the class. * @return boolean TRUE if the class exists as per the definition given above, FALSE otherwise. */ public static function classExists($className) { if (class_exists($className, false)) { return true; } foreach (spl_autoload_functions() as $loader) { if (is_array($loader)) { // array(???, ???) if (is_object($loader[0])) { if ($loader[0] instanceof ClassLoader) { // array($obj, 'methodName') if ($loader[0]->canLoadClass($className)) { return true; } } else if ($loader[0]->{$loader[1]}($className)) { return true; } } else if ($loader[0]::$loader[1]($className)) { // array('ClassName', 'methodName') return true; } } else if ($loader instanceof \Closure) { // function($className) {..} if ($loader($className)) { return true; } } else if (is_string($loader) && $loader($className)) { // "MyClass::loadClass" return true; } } return false; } /** * Gets the ClassLoader from the SPL autoload stack that is responsible * for (and is able to load) the class with the given name. * * @param string $className The name of the class. * @return The ClassLoader for the class or NULL if no such ClassLoader exists. */ public static function getClassLoader($className) { foreach (spl_autoload_functions() as $loader) { if (is_array($loader) && $loader[0] instanceof ClassLoader && $loader[0]->canLoadClass($className)) { return $loader[0]; } } return null; } }. */ namespace Doctrine\Common\Collections; use Closure, ArrayIterator; /** * An ArrayCollection is a Collection implementation that wraps a regular PHP array. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class ArrayCollection implements Collection { /** * An array containing the entries of this collection. * * @var array */ private $_elements; /** * Initializes a new ArrayCollection. * * @param array $elements */ public function __construct(array $elements = array()) { $this->_elements = $elements; } /** * Gets the PHP array representation of this collection. * * @return array The PHP array representation of this collection. */ public function toArray() { return $this->_elements; } /** * Sets the internal iterator to the first element in the collection and * returns this element. * * @return mixed */ public function first() { return reset($this->_elements); } /** * Sets the internal iterator to the last element in the collection and * returns this element. * * @return mixed */ public function last() { return end($this->_elements); } /** * Gets the current key/index at the current internal iterator position. * * @return mixed */ public function key() { return key($this->_elements); } /** * Moves the internal iterator position to the next element. * * @return mixed */ public function next() { return next($this->_elements); } /** * Gets the element of the collection at the current internal iterator position. * * @return mixed */ public function current() { return current($this->_elements); } /** * Removes an element with a specific key/index from the collection. * * @param mixed $key * @return mixed The removed element or NULL, if no element exists for the given key. */ public function remove($key) { if (isset($this->_elements[$key])) { $removed = $this->_elements[$key]; unset($this->_elements[$key]); return $removed; } return null; } /** * Removes the specified element from the collection, if it is found. * * @param mixed $element The element to remove. * @return boolean TRUE if this collection contained the specified element, FALSE otherwise. */ public function removeElement($element) { $key = array_search($element, $this->_elements, true); if ($key !== false) { unset($this->_elements[$key]); return true; } return false; } /** * ArrayAccess implementation of offsetExists() * * @see containsKey() */ public function offsetExists($offset) { return $this->containsKey($offset); } /** * ArrayAccess implementation of offsetGet() * * @see get() */ public function offsetGet($offset) { return $this->get($offset); } /** * ArrayAccess implementation of offsetGet() * * @see add() * @see set() */ public function offsetSet($offset, $value) { if ( ! isset($offset)) { return $this->add($value); } return $this->set($offset, $value); } /** * ArrayAccess implementation of offsetUnset() * * @see remove() */ public function offsetUnset($offset) { return $this->remove($offset); } /** * Checks whether the collection contains a specific key/index. * * @param mixed $key The key to check for. * @return boolean TRUE if the given key/index exists, FALSE otherwise. */ public function containsKey($key) { return isset($this->_elements[$key]); } /** * Checks whether the given element is contained in the collection. * Only element values are compared, not keys. The comparison of two elements * is strict, that means not only the value but also the type must match. * For objects this means reference equality. * * @param mixed $element * @return boolean TRUE if the given element is contained in the collection, * FALSE otherwise. */ public function contains($element) { return in_array($element, $this->_elements, true); } /** * Tests for the existance of an element that satisfies the given predicate. * * @param Closure $p The predicate. * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise. */ public function exists(Closure $p) { foreach ($this->_elements as $key => $element) if ($p($key, $element)) return true; return false; } /** * Searches for a given element and, if found, returns the corresponding key/index * of that element. The comparison of two elements is strict, that means not * only the value but also the type must match. * For objects this means reference equality. * * @param mixed $element The element to search for. * @return mixed The key/index of the element or FALSE if the element was not found. */ public function indexOf($element) { return array_search($element, $this->_elements, true); } /** * Gets the element with the given key/index. * * @param mixed $key The key. * @return mixed The element or NULL, if no element exists for the given key. */ public function get($key) { if (isset($this->_elements[$key])) { return $this->_elements[$key]; } return null; } /** * Gets all keys/indexes of the collection elements. * * @return array */ public function getKeys() { return array_keys($this->_elements); } /** * Gets all elements. * * @return array */ public function getValues() { return array_values($this->_elements); } /** * Returns the number of elements in the collection. * * Implementation of the Countable interface. * * @return integer The number of elements in the collection. */ public function count() { return count($this->_elements); } /** * Adds/sets an element in the collection at the index / with the specified key. * * When the collection is a Map this is like put(key,value)/add(key,value). * When the collection is a List this is like add(position,value). * * @param mixed $key * @param mixed $value */ public function set($key, $value) { $this->_elements[$key] = $value; } /** * Adds an element to the collection. * * @param mixed $value * @return boolean Always TRUE. */ public function add($value) { $this->_elements[] = $value; return true; } /** * Checks whether the collection is empty. * * Note: This is preferrable over count() == 0. * * @return boolean TRUE if the collection is empty, FALSE otherwise. */ public function isEmpty() { return ! $this->_elements; } /** * Gets an iterator for iterating over the elements in the collection. * * @return ArrayIterator */ public function getIterator() { return new ArrayIterator($this->_elements); } /** * Applies the given function to each element in the collection and returns * a new collection with the elements returned by the function. * * @param Closure $func * @return Collection */ public function map(Closure $func) { return new ArrayCollection(array_map($func, $this->_elements)); } /** * Returns all the elements of this collection that satisfy the predicate p. * The order of the elements is preserved. * * @param Closure $p The predicate used for filtering. * @return Collection A collection with the results of the filter operation. */ public function filter(Closure $p) { return new ArrayCollection(array_filter($this->_elements, $p)); } /** * Applies the given predicate p to all elements of this collection, * returning true, if the predicate yields true for all elements. * * @param Closure $p The predicate. * @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. */ public function forAll(Closure $p) { foreach ($this->_elements as $key => $element) { if ( ! $p($key, $element)) { return false; } } return true; } /** * Partitions this collection in two collections according to a predicate. * Keys are preserved in the resulting collections. * * @param Closure $p The predicate on which to partition. * @return array An array with two elements. The first element contains the collection * of elements where the predicate returned TRUE, the second element * contains the collection of elements where the predicate returned FALSE. */ public function partition(Closure $p) { $coll1 = $coll2 = array(); foreach ($this->_elements as $key => $element) { if ($p($key, $element)) { $coll1[$key] = $element; } else { $coll2[$key] = $element; } } return array(new ArrayCollection($coll1), new ArrayCollection($coll2)); } /** * Returns a string representation of this object. * * @return string */ public function __toString() { return __CLASS__ . '@' . spl_object_hash($this); } /** * Clears the collection. */ public function clear() { $this->_elements = array(); } /** * Extract a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset * @param int $length * @return array */ public function slice($offset, $length = null) { return array_slice($this->_elements, $offset, $length, true); } } . */ namespace Doctrine\Common\Collections; use Closure, Countable, IteratorAggregate, ArrayAccess; /** * The missing (SPL) Collection/Array/OrderedMap interface. * * A Collection resembles the nature of a regular PHP array. That is, * it is essentially an ordered map that can also be used * like a list. * * A Collection has an internal iterator just like a PHP array. In addition, * a Collection can be iterated with external iterators, which is preferrable. * To use an external iterator simply use the foreach language construct to * iterate over the collection (which calls {@link getIterator()} internally) or * explicitly retrieve an iterator though {@link getIterator()} which can then be * used to iterate over the collection. * You can not rely on the internal iterator of the collection being at a certain * position unless you explicitly positioned it before. Prefer iteration with * external iterators. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ interface Collection extends Countable, IteratorAggregate, ArrayAccess { /** * Adds an element at the end of the collection. * * @param mixed $element The element to add. * @return boolean Always TRUE. */ function add($element); /** * Clears the collection, removing all elements. */ function clear(); /** * Checks whether an element is contained in the collection. * This is an O(n) operation, where n is the size of the collection. * * @param mixed $element The element to search for. * @return boolean TRUE if the collection contains the element, FALSE otherwise. */ function contains($element); /** * Checks whether the collection is empty (contains no elements). * * @return boolean TRUE if the collection is empty, FALSE otherwise. */ function isEmpty(); /** * Removes the element at the specified index from the collection. * * @param string|integer $key The kex/index of the element to remove. * @return mixed The removed element or NULL, if the collection did not contain the element. */ function remove($key); /** * Removes the specified element from the collection, if it is found. * * @param mixed $element The element to remove. * @return boolean TRUE if this collection contained the specified element, FALSE otherwise. */ function removeElement($element); /** * Checks whether the collection contains an element with the specified key/index. * * @param string|integer $key The key/index to check for. * @return boolean TRUE if the collection contains an element with the specified key/index, * FALSE otherwise. */ function containsKey($key); /** * Gets the element at the specified key/index. * * @param string|integer $key The key/index of the element to retrieve. * @return mixed */ function get($key); /** * Gets all keys/indices of the collection. * * @return array The keys/indices of the collection, in the order of the corresponding * elements in the collection. */ function getKeys(); /** * Gets all values of the collection. * * @return array The values of all elements in the collection, in the order they * appear in the collection. */ function getValues(); /** * Sets an element in the collection at the specified key/index. * * @param string|integer $key The key/index of the element to set. * @param mixed $value The element to set. */ function set($key, $value); /** * Gets a native PHP array representation of the collection. * * @return array */ function toArray(); /** * Sets the internal iterator to the first element in the collection and * returns this element. * * @return mixed */ function first(); /** * Sets the internal iterator to the last element in the collection and * returns this element. * * @return mixed */ function last(); /** * Gets the key/index of the element at the current iterator position. * */ function key(); /** * Gets the element of the collection at the current iterator position. * */ function current(); /** * Moves the internal iterator position to the next element. * */ function next(); /** * Tests for the existence of an element that satisfies the given predicate. * * @param Closure $p The predicate. * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise. */ function exists(Closure $p); /** * Returns all the elements of this collection that satisfy the predicate p. * The order of the elements is preserved. * * @param Closure $p The predicate used for filtering. * @return Collection A collection with the results of the filter operation. */ function filter(Closure $p); /** * Applies the given predicate p to all elements of this collection, * returning true, if the predicate yields true for all elements. * * @param Closure $p The predicate. * @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. */ function forAll(Closure $p); /** * Applies the given function to each element in the collection and returns * a new collection with the elements returned by the function. * * @param Closure $func * @return Collection */ function map(Closure $func); /** * Partitions this collection in two collections according to a predicate. * Keys are preserved in the resulting collections. * * @param Closure $p The predicate on which to partition. * @return array An array with two elements. The first element contains the collection * of elements where the predicate returned TRUE, the second element * contains the collection of elements where the predicate returned FALSE. */ function partition(Closure $p); /** * Gets the index/key of a given element. The comparison of two elements is strict, * that means not only the value but also the type must match. * For objects this means reference equality. * * @param mixed $element The element to search for. * @return mixed The key/index of the element or FALSE if the element was not found. */ function indexOf($element); /** * Extract a slice of $length elements starting at position $offset from the Collection. * * If $length is null it returns all elements from $offset to the end of the Collection. * Keys have to be preserved by this method. Calling this method will only return the * selected slice and NOT change the elements contained in the collection slice is called on. * * @param int $offset * @param int $length * @return array */ public function slice($offset, $length = null); }. */ namespace Doctrine\Common; /** * Class to store and retrieve the version of Doctrine * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class Version { /** * Current Doctrine Version */ const VERSION = '2.0.0RC2-DEV'; /** * Compares a Doctrine version with the current one. * * @param string $version Doctrine version to compare. * @return int Returns -1 if older, 0 if it is the same, 1 if version * passed as argument is newer. */ public static function compare($version) { $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); $version = str_replace(' ', '', $version); return version_compare($version, $currentVersion); } }. */ namespace Doctrine\Common\Cache; /** * Xcache cache driver. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author David Abdemoulaie */ class XcacheCache extends AbstractCache { /** * {@inheritdoc} */ public function getIds() { $this->_checkAuth(); $keys = array(); for ($i = 0, $count = xcache_count(XC_TYPE_VAR); $i < $count; $i++) { $entries = xcache_list(XC_TYPE_VAR, $i); if (is_array($entries['cache_list'])) { foreach ($entries['cache_list'] as $entry) { $keys[] = $entry['name']; } } } return $keys; } /** * {@inheritdoc} */ protected function _doFetch($id) { return $this->_doContains($id) ? unserialize(xcache_get($id)) : false; } /** * {@inheritdoc} */ protected function _doContains($id) { return xcache_isset($id); } /** * {@inheritdoc} */ protected function _doSave($id, $data, $lifeTime = 0) { return xcache_set($id, serialize($data), (int) $lifeTime); } /** * {@inheritdoc} */ protected function _doDelete($id) { return xcache_unset($id); } /** * Checks that xcache.admin.enable_auth is Off * * @throws \BadMethodCallException When xcache.admin.enable_auth is On * @return void */ protected function _checkAuth() { if (ini_get('xcache.admin.enable_auth')) { throw new \BadMethodCallException('To use all features of \Doctrine\Common\Cache\XcacheCache, you must set "xcache.admin.enable_auth" to "Off" in your php.ini.'); } } }. */ namespace Doctrine\Common\Cache; /** * Array cache driver. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author David Abdemoulaie */ class ArrayCache extends AbstractCache { /** * @var array $data */ private $data = array(); /** * {@inheritdoc} */ public function getIds() { return array_keys($this->data); } /** * {@inheritdoc} */ protected function _doFetch($id) { if (isset($this->data[$id])) { return $this->data[$id]; } return false; } /** * {@inheritdoc} */ protected function _doContains($id) { return isset($this->data[$id]); } /** * {@inheritdoc} */ protected function _doSave($id, $data, $lifeTime = 0) { $this->data[$id] = $data; return true; } /** * {@inheritdoc} */ protected function _doDelete($id) { unset($this->data[$id]); return true; } }. */ namespace Doctrine\Common\Cache; use \Memcache; /** * Memcache cache driver. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author David Abdemoulaie */ class MemcacheCache extends AbstractCache { /** * @var Memcache */ private $_memcache; /** * Sets the memcache instance to use. * * @param Memcache $memcache */ public function setMemcache(Memcache $memcache) { $this->_memcache = $memcache; } /** * Gets the memcache instance used by the cache. * * @return Memcache */ public function getMemcache() { return $this->_memcache; } /** * {@inheritdoc} */ public function getIds() { $keys = array(); $allSlabs = $this->_memcache->getExtendedStats('slabs'); foreach ($allSlabs as $server => $slabs) { if (is_array($slabs)) { foreach (array_keys($slabs) as $slabId) { $dump = $this->_memcache->getExtendedStats('cachedump', (int) $slabId); if ($dump) { foreach ($dump as $entries) { if ($entries) { $keys = array_merge($keys, array_keys($entries)); } } } } } } return $keys; } /** * {@inheritdoc} */ protected function _doFetch($id) { return $this->_memcache->get($id); } /** * {@inheritdoc} */ protected function _doContains($id) { return (bool) $this->_memcache->get($id); } /** * {@inheritdoc} */ protected function _doSave($id, $data, $lifeTime = 0) { return $this->_memcache->set($id, $data, 0, (int) $lifeTime); } /** * {@inheritdoc} */ protected function _doDelete($id) { return $this->_memcache->delete($id); } }. */ namespace Doctrine\Common\Cache; /** * Interface for cache drivers. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ interface Cache { /** * Fetches an entry from the cache. * * @param string $id cache id The id of the cache entry to fetch. * @return string The cached data or FALSE, if no cache entry exists for the given id. */ function fetch($id); /** * Test if an entry exists in the cache. * * @param string $id cache id The cache id of the entry to check for. * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. */ function contains($id); /** * Puts data into the cache. * * @param string $id The cache id. * @param string $data The cache entry/data. * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime). * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. */ function save($id, $data, $lifeTime = 0); /** * Deletes a cache entry. * * @param string $id cache id * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. */ function delete($id); }. */ namespace Doctrine\Common\Cache; /** * APC cache driver. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision$ * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author David Abdemoulaie * @todo Rename: APCCache */ class ApcCache extends AbstractCache { /** * {@inheritdoc} */ public function getIds() { $ci = apc_cache_info('user'); $keys = array(); foreach ($ci['cache_list'] as $entry) { $keys[] = $entry['info']; } return $keys; } /** * {@inheritdoc} */ protected function _doFetch($id) { return apc_fetch($id); } /** * {@inheritdoc} */ protected function _doContains($id) { $found = false; apc_fetch($id, $found); return $found; } /** * {@inheritdoc} */ protected function _doSave($id, $data, $lifeTime = 0) { return (bool) apc_store($id, $data, (int) $lifeTime); } /** * {@inheritdoc} */ protected function _doDelete($id) { return apc_delete($id); } }. */ namespace Doctrine\Common\Cache; /** * Base class for cache driver implementations. * * @since 2.0 * @author Benjamin Eberlei * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ abstract class AbstractCache implements Cache { /** @var string The cache id to store the index of cache ids under */ private $_cacheIdsIndexId = 'doctrine_cache_ids'; /** @var string The namespace to prefix all cache ids with */ private $_namespace = null; /** * Set the namespace to prefix all cache ids with. * * @param string $namespace * @return void */ public function setNamespace($namespace) { $this->_namespace = $namespace; } /** * {@inheritdoc} */ public function fetch($id) { return $this->_doFetch($this->_getNamespacedId($id)); } /** * {@inheritdoc} */ public function contains($id) { return $this->_doContains($this->_getNamespacedId($id)); } /** * {@inheritdoc} */ public function save($id, $data, $lifeTime = 0) { return $this->_doSave($this->_getNamespacedId($id), $data, $lifeTime); } /** * {@inheritdoc} */ public function delete($id) { $id = $this->_getNamespacedId($id); if (strpos($id, '*') !== false) { return $this->deleteByRegex('/' . str_replace('*', '.*', $id) . '/'); } return $this->_doDelete($id); } /** * Delete all cache entries. * * @return array $deleted Array of the deleted cache ids */ public function deleteAll() { $ids = $this->getIds(); foreach ($ids as $id) { $this->delete($id); } return $ids; } /** * Delete cache entries where the id matches a PHP regular expressions * * @param string $regex * @return array $deleted Array of the deleted cache ids */ public function deleteByRegex($regex) { $deleted = array(); $ids = $this->getIds(); foreach ($ids as $id) { if (preg_match($regex, $id)) { $this->delete($id); $deleted[] = $id; } } return $deleted; } /** * Delete cache entries where the id has the passed prefix * * @param string $prefix * @return array $deleted Array of the deleted cache ids */ public function deleteByPrefix($prefix) { $deleted = array(); $prefix = $this->_getNamespacedId($prefix); $ids = $this->getIds(); foreach ($ids as $id) { if (strpos($id, $prefix) === 0) { $this->delete($id); $deleted[] = $id; } } return $deleted; } /** * Delete cache entries where the id has the passed suffix * * @param string $suffix * @return array $deleted Array of the deleted cache ids */ public function deleteBySuffix($suffix) { $deleted = array(); $ids = $this->getIds(); foreach ($ids as $id) { if (substr($id, -1 * strlen($suffix)) === $suffix) { $this->delete($id); $deleted[] = $id; } } return $deleted; } /** * Prefix the passed id with the configured namespace value * * @param string $id The id to namespace * @return string $id The namespaced id */ private function _getNamespacedId($id) { if ( ! $this->_namespace || strpos($id, $this->_namespace) === 0) { return $id; } else { return $this->_namespace . $id; } } /** * Fetches an entry from the cache. * * @param string $id cache id The id of the cache entry to fetch. * @return string The cached data or FALSE, if no cache entry exists for the given id. */ abstract protected function _doFetch($id); /** * Test if an entry exists in the cache. * * @param string $id cache id The cache id of the entry to check for. * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. */ abstract protected function _doContains($id); /** * Puts data into the cache. * * @param string $id The cache id. * @param string $data The cache entry/data. * @param int $lifeTime The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime). * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. */ abstract protected function _doSave($id, $data, $lifeTime = false); /** * Deletes a cache entry. * * @param string $id cache id * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. */ abstract protected function _doDelete($id); /** * Get an array of all the cache ids stored * * @return array $ids */ abstract public function getIds(); }. */ namespace Doctrine\Common\Util; /** * Static class containing most used debug methods. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @author Giorgio Sironi */ final class Debug { /** * Private constructor (prevents from instantiation) * */ private function __construct() {} /** * Prints a dump of the public, protected and private properties of $var. * * @static * @link http://xdebug.org/ * @param mixed $var * @param integer $maxDepth Maximum nesting level for object properties */ public static function dump($var, $maxDepth = 2) { ini_set('html_errors', 'On'); if (extension_loaded('xdebug')) { ini_set('xdebug.var_display_max_depth', $maxDepth); } $var = self::export($var, $maxDepth++); ob_start(); var_dump($var); $dump = ob_get_contents(); ob_end_clean(); echo strip_tags(html_entity_decode($dump)); ini_set('html_errors', 'Off'); } public static function export($var, $maxDepth) { $return = null; $isObj = is_object($var); if ($isObj && in_array('Doctrine\Common\Collections\Collection', class_implements($var))) { $var = $var->toArray(); } if ($maxDepth) { if (is_array($var)) { $return = array(); foreach ($var as $k => $v) { $return[$k] = self::export($v, $maxDepth - 1); } } else if ($isObj) { if ($var instanceof \DateTime) { $return = $var->format('c'); } else { $reflClass = new \ReflectionClass(get_class($var)); $return = new \stdclass(); $return->{'__CLASS__'} = get_class($var); if ($var instanceof \Doctrine\ORM\Proxy\Proxy && ! $var->__isInitialized__) { $reflProperty = $reflClass->getProperty('_identifier'); $reflProperty->setAccessible(true); foreach ($reflProperty->getValue($var) as $name => $value) { $return->$name = self::export($value, $maxDepth - 1); } } else { $excludeProperties = array(); if ($var instanceof \Doctrine\ORM\Proxy\Proxy) { $excludeProperties = array('_entityPersister', '__isInitialized__', '_identifier'); } foreach ($reflClass->getProperties() as $reflProperty) { $name = $reflProperty->getName(); if ( ! in_array($name, $excludeProperties)) { $reflProperty->setAccessible(true); $return->$name = self::export($reflProperty->getValue($var), $maxDepth - 1); } } } } } else { $return = $var; } } else { $return = is_object($var) ? get_class($var) : (is_array($var) ? 'Array(' . count($var) . ')' : $var); } return $return; } public static function toString($obj) { return method_exists('__toString', $obj) ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj); } } . */ namespace Doctrine\Common\Util; /** * Doctrine inflector has static methods for inflecting text * * The methods in these classes are from several different sources collected * across several different php projects and several different authors. The * original author names and emails are not known * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 1.0 * @version $Revision: 3189 $ * @author Konsta Vesterinen * @author Jonathan H. Wage */ class Inflector { /** * Convert word in to the format for a Doctrine table name. Converts 'ModelName' to 'model_name' * * @param string $word Word to tableize * @return string $word Tableized word */ public static function tableize($word) { return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); } /** * Convert a word in to the format for a Doctrine class name. Converts 'table_name' to 'TableName' * * @param string $word Word to classify * @return string $word Classified word */ public static function classify($word) { return str_replace(" ", "", ucwords(strtr($word, "_-", " "))); } /** * Camelize a word. This uses the classify() method and turns the first character to lowercase * * @param string $word * @return string $word */ public static function camelize($word) { return lcfirst(self::classify($word)); } }. */ namespace Doctrine\Common; /** * Contract for classes that provide the service of notifying listeners of * changes to their properties. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ interface NotifyPropertyChanged { /** * Adds a listener that wants to be notified about property changes. * * @param PropertyChangedListener $listener */ function addPropertyChangedListener(PropertyChangedListener $listener); } . */ namespace Doctrine\Common; /** * Contract for classes that are potential listeners of a NotifyPropertyChanged * implementor. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ interface PropertyChangedListener { /** * Notifies the listener of a property change. * * @param object $sender The object on which the property changed. * @param string $propertyName The name of the property that changed. * @param mixed $oldValue The old value of the property that changed. * @param mixed $newValue The new value of the property that changed. */ function propertyChanged($sender, $propertyName, $oldValue, $newValue); } . */ namespace Doctrine\Common; use Doctrine\Common\Events\Event; /** * The EventManager is the central point of Doctrine's event listener system. * Listeners are registered on the manager and events are dispatched through the * manager. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @version $Revision: 3938 $ * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ class EventManager { /** * Map of registered listeners. * => * * @var array */ private $_listeners = array(); /** * Dispatches an event to all registered listeners. * * @param string $eventName The name of the event to dispatch. The name of the event is * the name of the method that is invoked on listeners. * @param EventArgs $eventArgs The event arguments to pass to the event handlers/listeners. * If not supplied, the single empty EventArgs instance is used. * @return boolean */ public function dispatchEvent($eventName, EventArgs $eventArgs = null) { if (isset($this->_listeners[$eventName])) { $eventArgs = $eventArgs === null ? EventArgs::getEmptyInstance() : $eventArgs; foreach ($this->_listeners[$eventName] as $listener) { $listener->$eventName($eventArgs); } } } /** * Gets the listeners of a specific event or all listeners. * * @param string $event The name of the event. * @return array The event listeners for the specified event, or all event listeners. */ public function getListeners($event = null) { return $event ? $this->_listeners[$event] : $this->_listeners; } /** * Checks whether an event has any registered listeners. * * @param string $event * @return boolean TRUE if the specified event has any listeners, FALSE otherwise. */ public function hasListeners($event) { return isset($this->_listeners[$event]) && $this->_listeners[$event]; } /** * Adds an event listener that listens on the specified events. * * @param string|array $events The event(s) to listen on. * @param object $listener The listener object. */ public function addEventListener($events, $listener) { // Picks the hash code related to that listener $hash = spl_object_hash($listener); foreach ((array) $events as $event) { // Overrides listener if a previous one was associated already // Prevents duplicate listeners on same event (same instance only) $this->_listeners[$event][$hash] = $listener; } } /** * Removes an event listener from the specified events. * * @param string|array $events * @param object $listener */ public function removeEventListener($events, $listener) { // Picks the hash code related to that listener $hash = spl_object_hash($listener); foreach ((array) $events as $event) { // Check if actually have this listener associated if (isset($this->_listeners[$event][$hash])) { unset($this->_listeners[$event][$hash]); } } } /** * Adds an EventSubscriber. The subscriber is asked for all the events he is * interested in and added as a listener for these events. * * @param Doctrine\Common\EventSubscriber $subscriber The subscriber. */ public function addEventSubscriber(EventSubscriber $subscriber) { $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); } }. */ namespace Doctrine\Common; /** * Base class for writing simple lexers, i.e. for creating small DSLs. * * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel * @todo Rename: AbstractLexer */ abstract class Lexer { /** * @var array Array of scanned tokens */ private $tokens = array(); /** * @var integer Current lexer position in input string */ private $position = 0; /** * @var integer Current peek of current lexer position */ private $peek = 0; /** * @var array The next token in the input. */ public $lookahead; /** * @var array The last matched/seen token. */ public $token; /** * Sets the input data to be tokenized. * * The Lexer is immediately reset and the new input tokenized. * Any unprocessed tokens from any previous input are lost. * * @param string $input The input to be tokenized. */ public function setInput($input) { $this->tokens = array(); $this->reset(); $this->scan($input); } /** * Resets the lexer. */ public function reset() { $this->lookahead = null; $this->token = null; $this->peek = 0; $this->position = 0; } /** * Resets the peek pointer to 0. */ public function resetPeek() { $this->peek = 0; } /** * Resets the lexer position on the input to the given position. * * @param integer $position Position to place the lexical scanner */ public function resetPosition($position = 0) { $this->position = $position; } /** * Checks whether a given token matches the current lookahead. * * @param integer|string $token * @return boolean */ public function isNextToken($token) { return $this->lookahead['type'] === $token; } /** * Moves to the next token in the input string. * * A token is an associative array containing three items: * - 'value' : the string value of the token in the input string * - 'type' : the type of the token (identifier, numeric, string, input * parameter, none) * - 'position' : the position of the token in the input string * * @return array|null the next token; null if there is no more tokens left */ public function moveNext() { $this->peek = 0; $this->token = $this->lookahead; $this->lookahead = (isset($this->tokens[$this->position])) ? $this->tokens[$this->position++] : null; return $this->lookahead !== null; } /** * Tells the lexer to skip input tokens until it sees a token with the given value. * * @param $type The token type to skip until. */ public function skipUntil($type) { while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { $this->moveNext(); } } /** * Checks if given value is identical to the given token * * @param mixed $value * @param integer $token * @return boolean */ public function isA($value, $token) { return $this->getType($value) === $token; } /** * Moves the lookahead token forward. * * @return array | null The next token or NULL if there are no more tokens ahead. */ public function peek() { if (isset($this->tokens[$this->position + $this->peek])) { return $this->tokens[$this->position + $this->peek++]; } else { return null; } } /** * Peeks at the next token, returns it and immediately resets the peek. * * @return array|null The next token or NULL if there are no more tokens ahead. */ public function glimpse() { $peek = $this->peek(); $this->peek = 0; return $peek; } /** * Scans the input string for tokens. * * @param string $input a query string */ protected function scan($input) { static $regex; if ( ! isset($regex)) { $regex = '/(' . implode(')|(', $this->getCatchablePatterns()) . ')|' . implode('|', $this->getNonCatchablePatterns()) . '/i'; } $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($regex, $input, -1, $flags); foreach ($matches as $match) { // Must remain before 'value' assignment since it can change content $type = $this->getType($match[0]); $this->tokens[] = array( 'value' => $match[0], 'type' => $type, 'position' => $match[1], ); } } /** * Gets the literal for a given token. * * @param integer $token * @return string */ public function getLiteral($token) { $className = get_class($this); $reflClass = new \ReflectionClass($className); $constants = $reflClass->getConstants(); foreach ($constants as $name => $value) { if ($value === $token) { return $className . '::' . $name; } } return $token; } /** * Lexical catchable patterns. * * @return array */ abstract protected function getCatchablePatterns(); /** * Lexical non-catchable patterns. * * @return array */ abstract protected function getNonCatchablePatterns(); /** * Retrieve token type. Also processes the token value if necessary. * * @param string $value * @return integer */ abstract protected function getType(&$value); }. */ namespace Doctrine\Common; /** * An EventSubscriber knows himself what events he is interested in. * If an EventSubscriber is added to an EventManager, the manager invokes * {@link getSubscribedEvents} and registers the subscriber as a listener for all * returned events. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Roman Borschel */ interface EventSubscriber { /** * Returns an array of events this subscriber wants to listen to. * * @return array */ public function getSubscribedEvents(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Yaml offers convenience methods to load and dump YAML. * * @package symfony * @subpackage yaml * @author Fabien Potencier */ class Yaml { static protected $spec = '1.2'; /** * Sets the YAML specification version to use. * * @param string $version The YAML specification version */ static public function setSpecVersion($version) { if (!in_array($version, array('1.1', '1.2'))) { throw new \InvalidArgumentException(sprintf('Version %s of the YAML specifications is not supported', $version)); } self::$spec = $version; } /** * Gets the YAML specification version to use. * * @return string The YAML specification version */ static public function getSpecVersion() { return self::$spec; } /** * Loads YAML into a PHP array. * * The load method, when supplied with a YAML stream (string or file), * will do its best to convert YAML in a file into a PHP array. * * Usage: * * $array = Yaml::load('config.yml'); * print_r($array); * * * @param string $input Path of YAML file or string containing YAML * * @return array The YAML converted to a PHP array * * @throws \InvalidArgumentException If the YAML is not valid */ public static function load($input) { $file = ''; // if input is a file, process it if (strpos($input, "\n") === false && is_file($input)) { $file = $input; ob_start(); $retval = include($input); $content = ob_get_clean(); // if an array is returned by the config file assume it's in plain php form else in YAML $input = is_array($retval) ? $retval : $content; } // if an array is returned by the config file assume it's in plain php form else in YAML if (is_array($input)) { return $input; } $yaml = new Parser(); try { $ret = $yaml->parse($input); } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('Unable to parse %s: %s', $file ? sprintf('file "%s"', $file) : 'string', $e->getMessage())); } return $ret; } /** * Dumps a PHP array to a YAML string. * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. * * @param array $array PHP array * @param integer $inline The level where you switch to inline YAML * * @return string A YAML string representing the original PHP array */ public static function dump($array, $inline = 2) { $yaml = new Dumper(); return $yaml->dump($array, $inline); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Dumper dumps PHP variables to YAML strings. * * @package symfony * @subpackage yaml * @author Fabien Potencier */ class Dumper { /** * Dumps a PHP value to YAML. * * @param mixed $input The PHP value * @param integer $inline The level where you switch to inline YAML * @param integer $indent The level o indentation indentation (used internally) * * @return string The YAML representation of the PHP value */ public function dump($input, $inline = 0, $indent = 0) { $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; if ($inline <= 0 || !is_array($input) || empty($input)) { $output .= $prefix.Inline::dump($input); } else { $isAHash = array_keys($input) !== range(0, count($input) - 1); foreach ($input as $key => $value) { $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); $output .= sprintf('%s%s%s%s', $prefix, $isAHash ? Inline::dump($key).':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 2) ).($willBeInlined ? "\n" : ''); } } return $output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Exception class used by all exceptions thrown by the component. * * @package symfony * @subpackage yaml * @author Fabien Potencier */ class ParserException extends Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Exception class used by all exceptions thrown by the component. * * @package symfony * @subpackage yaml * @author Fabien Potencier */ class Exception extends \Exception { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Inline implements a YAML parser/dumper for the YAML inline syntax. * * @package symfony * @subpackage yaml * @author Fabien Potencier */ class Inline { const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; /** * Convert a YAML string to a PHP array. * * @param string $value A YAML string * * @return array A PHP array representing the YAML string */ static public function load($value) { $value = trim($value); if (0 == strlen($value)) { return ''; } switch ($value[0]) { case '[': return self::parseSequence($value); case '{': return self::parseMapping($value); default: return self::parseScalar($value); } } /** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * * @return string The YAML string representing the PHP array */ static public function dump($value) { $trueValues = '1.1' == Yaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); $falseValues = '1.1' == Yaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); switch (true) { case is_resource($value): throw new Exception('Unable to dump PHP resources in a YAML file.'); case is_object($value): return '!!php/object:'.serialize($value); case is_array($value): return self::dumpArray($value); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'$value'" : (int) $value; case is_numeric($value): return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value); case false !== strpos($value, "\n") || false !== strpos($value, "\r"): return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value)); case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ - ? | < > = ! % @ ` ]/x', $value): return sprintf("'%s'", str_replace('\'', '\'\'', $value)); case '' == $value: return "''"; case preg_match(self::getTimestampRegex(), $value): return "'$value'"; case in_array(strtolower($value), $trueValues): return "'$value'"; case in_array(strtolower($value), $falseValues): return "'$value'"; case in_array(strtolower($value), array('null', '~')): return "'$value'"; default: return $value; } } /** * Dumps a PHP array to a YAML string. * * @param array $value The PHP array to dump * * @return string The YAML string representing the PHP array */ static protected function dumpArray($value) { // array $keys = array_keys($value); if ( (1 == count($keys) && '0' == $keys[0]) || (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (integer) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2)) { $output = array(); foreach ($value as $val) { $output[] = self::dump($val); } return sprintf('[%s]', implode(', ', $output)); } // mapping $output = array(); foreach ($value as $key => $val) { $output[] = sprintf('%s: %s', self::dump($key), self::dump($val)); } return sprintf('{ %s }', implode(', ', $output)); } /** * Parses a scalar to a YAML string. * * @param scalar $scalar * @param string $delimiters * @param array $stringDelimiter * @param integer $i * @param boolean $evaluate * * @return string A YAML string */ static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) { if (in_array($scalar[$i], $stringDelimiters)) { // quoted scalar $output = self::parseQuotedScalar($scalar, $i); } else { // "normal" string if (!$delimiters) { $output = substr($scalar, $i); $i += strlen($output); // remove comments if (false !== $strpos = strpos($output, ' #')) { $output = rtrim(substr($output, 0, $strpos)); } } else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += strlen($output); } else { throw new ParserException(sprintf('Malformed inline YAML string (%s).', $scalar)); } $output = $evaluate ? self::evaluateScalar($output) : $output; } return $output; } /** * Parses a quoted scalar to YAML. * * @param string $scalar * @param integer $i * * @return string A YAML string */ static protected function parseQuotedScalar($scalar, &$i) { if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/A', substr($scalar, $i), $match)) { throw new ParserException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); } $output = substr($match[0], 1, strlen($match[0]) - 2); if ('"' == $scalar[$i]) { // evaluate the string $output = str_replace(array('\\"', '\\n', '\\r'), array('"', "\n", "\r"), $output); } else { // unescape ' $output = str_replace('\'\'', '\'', $output); } $i += strlen($match[0]); return $output; } /** * Parses a sequence to a YAML string. * * @param string $sequence * @param integer $i * * @return string A YAML string */ static protected function parseSequence($sequence, &$i = 0) { $output = array(); $len = strlen($sequence); $i += 1; // [foo, bar, ...] while ($i < $len) { switch ($sequence[$i]) { case '[': // nested sequence $output[] = self::parseSequence($sequence, $i); break; case '{': // nested mapping $output[] = self::parseMapping($sequence, $i); break; case ']': return $output; case ',': case ' ': break; default: $isQuoted = in_array($sequence[$i], array('"', "'")); $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); if (!$isQuoted && false !== strpos($value, ': ')) { // embedded mapping? try { $value = self::parseMapping('{'.$value.'}'); } catch (\InvalidArgumentException $e) { // no, it's not } } $output[] = $value; --$i; } ++$i; } throw new ParserException(sprintf('Malformed inline YAML string %s', $sequence)); } /** * Parses a mapping to a YAML string. * * @param string $mapping * @param integer $i * * @return string A YAML string */ static protected function parseMapping($mapping, &$i = 0) { $output = array(); $len = strlen($mapping); $i += 1; // {foo: bar, bar:foo, ...} while ($i < $len) { switch ($mapping[$i]) { case ' ': case ',': ++$i; continue 2; case '}': return $output; } // key $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); // value $done = false; while ($i < $len) { switch ($mapping[$i]) { case '[': // nested sequence $output[$key] = self::parseSequence($mapping, $i); $done = true; break; case '{': // nested mapping $output[$key] = self::parseMapping($mapping, $i); $done = true; break; case ':': case ' ': break; default: $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); $done = true; --$i; } ++$i; if ($done) { continue 2; } } } throw new ParserException(sprintf('Malformed inline YAML string %s', $mapping)); } /** * Evaluates scalars and replaces magic values. * * @param string $scalar * * @return string A YAML string */ static protected function evaluateScalar($scalar) { $scalar = trim($scalar); $trueValues = '1.1' == Yaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); $falseValues = '1.1' == Yaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); switch (true) { case 'null' == strtolower($scalar): case '' == $scalar: case '~' == $scalar: return null; case 0 === strpos($scalar, '!str'): return (string) substr($scalar, 5); case 0 === strpos($scalar, '! '): return intval(self::parseScalar(substr($scalar, 2))); case 0 === strpos($scalar, '!!php/object:'): return unserialize(substr($scalar, 13)); case ctype_digit($scalar): $raw = $scalar; $cast = intval($scalar); return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); case in_array(strtolower($scalar), $trueValues): return true; case in_array(strtolower($scalar), $falseValues): return false; case is_numeric($scalar): return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); case 0 == strcasecmp($scalar, '.inf'): case 0 == strcasecmp($scalar, '.NaN'): return -log(0); case 0 == strcasecmp($scalar, '-.inf'): return log(0); case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): return floatval(str_replace(',', '', $scalar)); case preg_match(self::getTimestampRegex(), $scalar): return strtotime($scalar); default: return (string) $scalar; } } static protected function getTimestampRegex() { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? $~x EOF; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Parser parses YAML strings to convert them to PHP arrays. * * @package symfony * @subpackage yaml * @author Fabien Potencier */ class Parser { protected $offset = 0; protected $lines = array(); protected $currentLineNb = -1; protected $currentLine = ''; protected $refs = array(); /** * Constructor * * @param integer $offset The offset of YAML document (used for line numbers in error messages) */ public function __construct($offset = 0) { $this->offset = $offset; } /** * Parses a YAML string to a PHP value. * * @param string $value A YAML string * * @return mixed A PHP value * * @throws \InvalidArgumentException If the YAML is not valid */ public function parse($value) { $this->currentLineNb = -1; $this->currentLine = ''; $this->lines = explode("\n", $this->cleanup($value)); $data = array(); while ($this->moveToNextLine()) { if ($this->isCurrentLineEmpty()) { continue; } // tab? if (preg_match('#^\t+#', $this->currentLine)) { throw new ParserException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine)); } $isRef = $isInPlace = $isProcessed = false; if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#', $this->currentLine, $values)) { if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } // array if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; $data[] = $parser->parse($this->getNextEmbedBlock()); } else { if (isset($values['leadspaces']) && ' ' == $values['leadspaces'] && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{].*?) *\:(\s+(?P.+?))?\s*$#', $values['value'], $matches)) { // this is a compact notation element, add to next block and parse $c = $this->getRealCurrentLineNb(); $parser = new Parser($c); $parser->refs =& $this->refs; $block = $values['value']; if (!$this->isNextLineIndented()) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2); } $data[] = $parser->parse($block); } else { $data[] = $this->parseValue($values['value']); } } } else if (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"].*?) *\:(\s+(?P.+?))?\s*$#', $this->currentLine, $values)) { $key = Inline::parseScalar($values['key']); if ('<<' === $key) { if (isset($values['value']) && '*' === substr($values['value'], 0, 1)) { $isInPlace = substr($values['value'], 1); if (!array_key_exists($isInPlace, $this->refs)) { throw new ParserException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine)); } } else { if (isset($values['value']) && $values['value'] !== '') { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; $parsed = $parser->parse($value); $merged = array(); if (!is_array($parsed)) { throw new ParserException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() + 1, $this->currentLine)); } else if (isset($parsed[0])) { // Numeric array, merge individual elements foreach (array_reverse($parsed) as $parsedItem) { if (!is_array($parsedItem)) { throw new ParserException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem)); } $merged = array_merge($parsedItem, $merged); } } else { // Associative array, merge $merged = array_merge($merge, $parsed); } $isProcessed = $merged; } } else if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } if ($isProcessed) { // Merge keys $data = $isProcessed; } // hash else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { // if next line is less indented or equal, then it means that the current value is null if ($this->isNextLineIndented()) { $data[$key] = null; } else { $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; $data[$key] = $parser->parse($this->getNextEmbedBlock()); } } else { if ($isInPlace) { $data = $this->refs[$isInPlace]; } else { $data[$key] = $this->parseValue($values['value']); } } } else { // 1-liner followed by newline if (2 == count($this->lines) && empty($this->lines[1])) { $value = Inline::load($this->lines[0]); if (is_array($value)) { $first = reset($value); if ('*' === substr($first, 0, 1)) { $data = array(); foreach ($value as $alias) { $data[] = $this->refs[substr($alias, 1)]; } $value = $data; } } return $value; } switch (preg_last_error()) { case PREG_INTERNAL_ERROR: $error = 'Internal PCRE error on line'; break; case PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached on line'; break; case PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached on line'; break; case PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data on line'; break; case PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line'; break; default: $error = 'Unable to parse line'; } throw new ParserException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine)); } if ($isRef) { $this->refs[$isRef] = end($data); } } return empty($data) ? null : $data; } /** * Returns the current line number (takes the offset into account). * * @return integer The current line number */ protected function getRealCurrentLineNb() { return $this->currentLineNb + $this->offset; } /** * Returns the current line indentation. * * @return integer The current line indentation */ protected function getCurrentLineIndentation() { return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); } /** * Returns the next embed block of YAML. * * @param integer $indentation The indent level at which the block is to be read, or null for default * * @return string A YAML string */ protected function getNextEmbedBlock($indentation = null) { $this->moveToNextLine(); if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); if (!$this->isCurrentLineEmpty() && 0 == $newIndent) { throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); } } else { $newIndent = $indentation; } $data = array(substr($this->currentLine, $newIndent)); while ($this->moveToNextLine()) { if ($this->isCurrentLineEmpty()) { if ($this->isCurrentLineBlank()) { $data[] = substr($this->currentLine, $newIndent); } continue; } $indent = $this->getCurrentLineIndentation(); if (preg_match('#^(?P *)$#', $this->currentLine, $match)) { // empty line $data[] = $match['text']; } else if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } else if (0 == $indent) { $this->moveToPreviousLine(); break; } else { throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine)); } } return implode("\n", $data); } /** * Moves the parser to the next line. */ protected function moveToNextLine() { if ($this->currentLineNb >= count($this->lines) - 1) { return false; } $this->currentLine = $this->lines[++$this->currentLineNb]; return true; } /** * Moves the parser to the previous line. */ protected function moveToPreviousLine() { $this->currentLine = $this->lines[--$this->currentLineNb]; } /** * Parses a YAML value. * * @param string $value A YAML value * * @return mixed A PHP value */ protected function parseValue($value) { if ('*' === substr($value, 0, 1)) { if (false !== $pos = strpos($value, '#')) { $value = substr($value, 1, $pos - 2); } else { $value = substr($value, 1); } if (!array_key_exists($value, $this->refs)) { throw new ParserException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine)); } return $this->refs[$value]; } if (preg_match('/^(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?$/', $value, $matches)) { $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); } else { return Inline::load($value); } } /** * Parses a folded scalar. * * @param string $separator The separator that was used to begin this folded scalar (| or >) * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) * @param integer $indentation The indentation that was used to begin this folded scalar * * @return string The text value */ protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0) { $separator = '|' == $separator ? "\n" : ' '; $text = ''; $notEOF = $this->moveToNextLine(); while ($notEOF && $this->isCurrentLineBlank()) { $text .= "\n"; $notEOF = $this->moveToNextLine(); } if (!$notEOF) { return ''; } if (!preg_match('#^(?P'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P.*)$#', $this->currentLine, $matches)) { $this->moveToPreviousLine(); return ''; } $textIndent = $matches['indent']; $previousIndent = 0; $text .= $matches['text'].$separator; while ($this->currentLineNb + 1 < count($this->lines)) { $this->moveToNextLine(); if (preg_match('#^(?P {'.strlen($textIndent).',})(?P.+)$#', $this->currentLine, $matches)) { if (' ' == $separator && $previousIndent != $matches['indent']) { $text = substr($text, 0, -1)."\n"; } $previousIndent = $matches['indent']; $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator); } else if (preg_match('#^(?P *)$#', $this->currentLine, $matches)) { $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n"; } else { $this->moveToPreviousLine(); break; } } if (' ' == $separator) { // replace last separator by a newline $text = preg_replace('/ (\n*)$/', "\n$1", $text); } switch ($indicator) { case '': $text = preg_replace('#\n+$#s', "\n", $text); break; case '+': break; case '-': $text = preg_replace('#\n+$#s', '', $text); break; } return $text; } /** * Returns true if the next line is indented. * * @return Boolean Returns true if the next line is indented, false otherwise */ protected function isNextLineIndented() { $currentIndentation = $this->getCurrentLineIndentation(); $notEOF = $this->moveToNextLine(); while ($notEOF && $this->isCurrentLineEmpty()) { $notEOF = $this->moveToNextLine(); } if (false === $notEOF) { return false; } $ret = false; if ($this->getCurrentLineIndentation() <= $currentIndentation) { $ret = true; } $this->moveToPreviousLine(); return $ret; } /** * Returns true if the current line is blank or if it is a comment line. * * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise */ protected function isCurrentLineEmpty() { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } /** * Returns true if the current line is blank. * * @return Boolean Returns true if the current line is blank, false otherwise */ protected function isCurrentLineBlank() { return '' == trim($this->currentLine, ' '); } /** * Returns true if the current line is a comment line. * * @return Boolean Returns true if the current line is a comment line, false otherwise */ protected function isCurrentLineComment() { //checking explicitly the first char of the trim is faster than loops or strpos $ltrimmedLine = ltrim($this->currentLine, ' '); return $ltrimmedLine[0] === '#'; } /** * Cleanups a YAML string to be parsed. * * @param string $value The input YAML string * * @return string A cleaned up YAML string */ protected function cleanup($value) { $value = str_replace(array("\r\n", "\r"), "\n", $value); if (!preg_match("#\n$#", $value)) { $value .= "\n"; } // strip YAML header $count = 0; $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value, -1, $count); $this->offset += $count; // remove leading comments and/or --- $trimmedValue = preg_replace('#^((\#.*?\n)|(\-\-\-.*?\n))*#s', '', $value, -1, $count); if ($count == 1) { // items have been removed, update the offset $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; } return $value; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier */ class ListCommand extends Command { /** * @see Command */ protected function configure() { $this ->setDefinition(array( new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('xml', null, InputOption::PARAMETER_NONE, 'To output help as XML'), )) ->setName('list') ->setDescription('Lists commands') ->setHelp(<<list command lists all commands: ./symfony list You can also display the commands for a specific namespace: ./symfony list test You can also output the information as XML by using the --xml option: ./symfony list --xml EOF ); } /** * @see Command */ protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('xml')) { $output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW); } else { $output->writeln($this->application->asText($input->getArgument('namespace'))); } } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * HelpCommand displays the help for a given command. * * @author Fabien Potencier */ class HelpCommand extends Command { protected $command; /** * @see Command */ protected function configure() { $this->ignoreValidationErrors = true; $this ->setDefinition(array( new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('xml', null, InputOption::PARAMETER_NONE, 'To output help as XML'), )) ->setName('help') ->setAliases(array('?')) ->setDescription('Displays help for a command') ->setHelp(<<help command displays help for a given command: ./symfony help list You can also output the help as XML by using the --xml option: ./symfony help --xml list EOF ); } public function setCommand(Command $command) { $this->command = $command; } /** * @see Command */ protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->application->getCommand($input->getArgument('command_name')); } if ($input->getOption('xml')) { $output->writeln($this->command->asXml(), Output::OUTPUT_RAW); } else { $output->writeln($this->command->asText()); } } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * Base class for all commands. * * @author Fabien Potencier */ class Command { protected $name; protected $namespace; protected $aliases; protected $definition; protected $help; protected $application; protected $description; protected $ignoreValidationErrors; protected $applicationDefinitionMerged; protected $code; /** * Constructor. * * @param string $name The name of the command * * @throws \LogicException When the command name is empty */ public function __construct($name = null) { $this->definition = new InputDefinition(); $this->ignoreValidationErrors = false; $this->applicationDefinitionMerged = false; $this->aliases = array(); if (null !== $name) { $this->setName($name); } $this->configure(); if (!$this->name) { throw new \LogicException('The command name cannot be empty.'); } } /** * Sets the application instance for this command. * * @param Application $application An Application instance */ public function setApplication(Application $application = null) { $this->application = $application; } /** * Configures the current command. */ protected function configure() { } /** * Executes the current command. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return integer 0 if everything went fine, or an error code * * @throws \LogicException When this abstract class is not implemented */ protected function execute(InputInterface $input, OutputInterface $output) { throw new \LogicException('You must override the execute() method in the concrete command class.'); } /** * Interacts with the user. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function interact(InputInterface $input, OutputInterface $output) { } /** * Initializes the command just after the input has been validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialized based on the input arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function initialize(InputInterface $input, OutputInterface $output) { } /** * Runs the command. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ public function run(InputInterface $input, OutputInterface $output) { // add the application arguments and options $this->mergeApplicationDefinition(); // bind the input against the command specific arguments/options try { $input->bind($this->definition); } catch (\Exception $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if ($input->isInteractive()) { $this->interact($input, $output); } $input->validate(); if ($this->code) { return call_user_func($this->code, $input, $output); } else { return $this->execute($input, $output); } } /** * Sets the code to execute when running this command. * * @param \Closure $code A \Closure * * @return Command The current instance */ public function setCode(\Closure $code) { $this->code = $code; return $this; } /** * Merges the application definition with the command definition. */ protected function mergeApplicationDefinition() { if (null === $this->application || true === $this->applicationDefinitionMerged) { return; } $this->definition->setArguments(array_merge( $this->application->getDefinition()->getArguments(), $this->definition->getArguments() )); $this->definition->addOptions($this->application->getDefinition()->getOptions()); $this->applicationDefinitionMerged = true; } /** * Sets an array of argument and option instances. * * @param array|Definition $definition An array of argument and option instances or a definition instance * * @return Command The current instance */ public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } /** * Gets the InputDefinition attached to this Command. * * @return InputDefinition An InputDefinition instance */ public function getDefinition() { return $this->definition; } /** * Adds an argument. * * @param string $name The argument name * @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * * @return Command The current instance */ public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } /** * Adds an option. * * @param string $name The option name * @param string $shortcut The shortcut (can be null) * @param integer $mode The option mode: self::PARAMETER_REQUIRED, self::PARAMETER_NONE or self::PARAMETER_OPTIONAL * @param string $description A description text * @param mixed $default The default value (must be null for self::PARAMETER_REQUIRED or self::PARAMETER_NONE) * * @return Command The current instance */ public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } /** * Sets the name of the command. * * This method can set both the namespace and the name if * you separate them by a colon (:) * * $command->setName('foo:bar'); * * @param string $name The command name * * @return Command The current instance * * @throws \InvalidArgumentException When command name given is empty */ public function setName($name) { if (false !== $pos = strrpos($name, ':')) { $namespace = substr($name, 0, $pos); $name = substr($name, $pos + 1); } else { $namespace = $this->namespace; } if (!$name) { throw new \InvalidArgumentException('A command name cannot be empty.'); } $this->namespace = $namespace; $this->name = $name; return $this; } /** * Returns the command namespace. * * @return string The command namespace */ public function getNamespace() { return $this->namespace; } /** * Returns the command name * * @return string The command name */ public function getName() { return $this->name; } /** * Returns the fully qualified command name. * * @return string The fully qualified command name */ public function getFullName() { return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName(); } /** * Sets the description for the command. * * @param string $description The description for the command * * @return Command The current instance */ public function setDescription($description) { $this->description = $description; return $this; } /** * Returns the description for the command. * * @return string The description for the command */ public function getDescription() { return $this->description; } /** * Sets the help for the command. * * @param string $help The help for the command * * @return Command The current instance */ public function setHelp($help) { $this->help = $help; return $this; } /** * Returns the help for the command. * * @return string The help for the command */ public function getHelp() { return $this->help; } /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. * * @return string The processed help for the command */ public function getProcessedHelp() { $name = $this->namespace.':'.$this->name; $placeholders = array( '%command.name%', '%command.full_name%' ); $replacements = array( $name, $_SERVER['PHP_SELF'].' '.$name ); return str_replace($placeholders, $replacements, $this->getHelp()); } /** * Sets the aliases for the command. * * @param array $aliases An array of aliases for the command * * @return Command The current instance */ public function setAliases($aliases) { $this->aliases = $aliases; return $this; } /** * Returns the aliases for the command. * * @return array An array of aliases for the command */ public function getAliases() { return $this->aliases; } /** * Returns the synopsis for the command. * * @return string The synopsis */ public function getSynopsis() { return sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis()); } /** * Gets a helper instance by name. * * @param string $name The helper name * * @return mixed The helper value * * @throws \InvalidArgumentException if the helper is not defined */ protected function getHelper($name) { return $this->application->getHelperSet()->get($name); } /** * Gets a helper instance by name. * * @param string $name The helper name * * @return mixed The helper value * * @throws \InvalidArgumentException if the helper is not defined */ public function __get($name) { return $this->application->getHelperSet()->get($name); } /** * Returns a text representation of the command. * * @return string A string representing the command */ public function asText() { $messages = array( 'Usage:', ' '.$this->getSynopsis(), '', ); if ($this->getAliases()) { $messages[] = 'Aliases: '.implode(', ', $this->getAliases()).''; } $messages[] = $this->definition->asText(); if ($help = $this->getProcessedHelp()) { $messages[] = 'Help:'; $messages[] = ' '.implode("\n ", explode("\n", $help))."\n"; } return implode("\n", $messages); } /** * Returns an XML representation of the command. * * @param Boolean $asDom Whether to return a DOM or an XML string * * @return string|DOMDocument An XML string representing the command */ public function asXml($asDom = false) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $dom->appendChild($commandXML = $dom->createElement('command')); $commandXML->setAttribute('id', $this->getFullName()); $commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global'); $commandXML->setAttribute('name', $this->getName()); $commandXML->appendChild($usageXML = $dom->createElement('usage')); $usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), ''))); $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription())))); $commandXML->appendChild($helpXML = $dom->createElement('help')); $help = $this->help; $helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help)))); $commandXML->appendChild($aliasesXML = $dom->createElement('aliases')); foreach ($this->getAliases() as $alias) { $aliasesXML->appendChild($aliasXML = $dom->createElement('alias')); $aliasXML->appendChild($dom->createTextNode($alias)); } $definition = $this->definition->asXml(true); $commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true)); $commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true)); return $asDom ? $dom : $dom->saveXml(); } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * ConsoleOutput is the default class for all CLI output. It uses STDOUT. * * This class is a convenient wrapper around `StreamOutput`. * * $output = new ConsoleOutput(); * * This is equivalent to: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * @author Fabien Potencier */ class ConsoleOutput extends StreamOutput { /** * Constructor. * * @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE) * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing) */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null) { parent::__construct(fopen('php://stdout', 'w'), $verbosity, $decorated); } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier */ interface OutputInterface { /** * Writes a message to the output. * * @param string|array $messages The message as an array of lines of a single string * @param Boolean $newline Whether to add a newline or not * @param integer $type The type of output * * @throws \InvalidArgumentException When unknown output type is given */ function write($messages, $newline = false, $type = 0); /** * Sets the verbosity of the output. * * @param integer $level The level of verbosity */ function setVerbosity($level); /** * Sets the decorated flag. * * @param Boolean $decorated Whether to decorated the messages or not */ function setDecorated($decorated); } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * StreamOutput writes the output to a given stream. * * Usage: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * As `StreamOutput` can use any stream, you can also use a file: * * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * * @author Fabien Potencier */ class StreamOutput extends Output { protected $stream; /** * Constructor. * * @param mixed $stream A stream resource * @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE) * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing) * * @throws \InvalidArgumentException When first argument is not a real stream */ public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null) { if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); } $this->stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport($decorated); } parent::__construct($verbosity, $decorated); } /** * Gets the stream attached to this StreamOutput instance. * * @return resource A stream resource */ public function getStream() { return $this->stream; } /** * Writes a message to the output. * * @param string $message A message to write to the output * @param Boolean $newline Whether to add a newline or not * * @throws \RuntimeException When unable to write output (should never happen) */ public function doWrite($message, $newline) { if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) { // @codeCoverageIgnoreStart // should never happen throw new \RuntimeException('Unable to write output.'); // @codeCoverageIgnoreEnd } flush(); } /** * Returns true if the stream supports colorization. * * Colorization is disabled if not supported by the stream: * * - windows without ansicon * - non tty consoles * * @return Boolean true if the stream supports colorization, false otherwise */ protected function hasColorSupport() { // @codeCoverageIgnoreStart if (DIRECTORY_SEPARATOR == '\\') { return false !== getenv('ANSICON'); } else { return function_exists('posix_isatty') && @posix_isatty($this->stream); } // @codeCoverageIgnoreEnd } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * Base class for output classes. * * There is three level of verbosity: * * * normal: no option passed (normal output - information) * * verbose: -v (more output - debug) * * quiet: -q (no output) * * @author Fabien Potencier */ abstract class Output implements OutputInterface { const VERBOSITY_QUIET = 0; const VERBOSITY_NORMAL = 1; const VERBOSITY_VERBOSE = 2; const OUTPUT_NORMAL = 0; const OUTPUT_RAW = 1; const OUTPUT_PLAIN = 2; protected $verbosity; protected $decorated; static protected $styles = array( 'error' => array('bg' => 'red', 'fg' => 'white'), 'info' => array('fg' => 'green'), 'comment' => array('fg' => 'yellow'), 'question' => array('bg' => 'cyan', 'fg' => 'black'), ); static protected $options = array('bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8); static protected $foreground = array('black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37); static protected $background = array('black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47); /** * Constructor. * * @param integer $verbosity The verbosity level (self::VERBOSITY_QUIET, self::VERBOSITY_NORMAL, self::VERBOSITY_VERBOSE) * @param Boolean $decorated Whether to decorate messages or not (null for auto-guessing) */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null) { $this->decorated = (Boolean) $decorated; $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; } /** * Sets a new style. * * @param string $name The style name * @param array $options An array of options */ static public function setStyle($name, $options = array()) { static::$styles[strtolower($name)] = $options; } /** * Sets the decorated flag. * * @param Boolean $decorated Whether to decorated the messages or not */ public function setDecorated($decorated) { $this->decorated = (Boolean) $decorated; } /** * Gets the decorated flag. * * @return Boolean true if the output will decorate messages, false otherwise */ public function isDecorated() { return $this->decorated; } /** * Sets the verbosity of the output. * * @param integer $level The level of verbosity */ public function setVerbosity($level) { $this->verbosity = (int) $level; } /** * Gets the current verbosity of the output. * * @return integer The current level of verbosity */ public function getVerbosity() { return $this->verbosity; } /** * Writes a message to the output and adds a newline at the end. * * @param string|array $messages The message as an array of lines of a single string * @param integer $type The type of output */ public function writeln($messages, $type = 0) { $this->write($messages, true, $type); } /** * Writes a message to the output. * * @param string|array $messages The message as an array of lines of a single string * @param Boolean $newline Whether to add a newline or not * @param integer $type The type of output * * @throws \InvalidArgumentException When unknown output type is given */ public function write($messages, $newline = false, $type = 0) { if (self::VERBOSITY_QUIET === $this->verbosity) { return; } if (!is_array($messages)) { $messages = array($messages); } foreach ($messages as $message) { switch ($type) { case Output::OUTPUT_NORMAL: $message = $this->format($message); break; case Output::OUTPUT_RAW: break; case Output::OUTPUT_PLAIN: $message = strip_tags($this->format($message)); break; default: throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); } $this->doWrite($message, $newline); } } /** * Writes a message to the output. * * @param string $message A message to write to the output * @param Boolean $newline Whether to add a newline or not */ abstract public function doWrite($message, $newline); /** * Formats a message according to the given styles. * * @param string $message The message to style * * @return string The styled message */ protected function format($message) { $message = preg_replace_callback('#<([a-z][a-z0-9\-_=;]+)>#i', array($this, 'replaceStartStyle'), $message); return preg_replace_callback('##i', array($this, 'replaceEndStyle'), $message); } /** * @throws \InvalidArgumentException When style is unknown */ protected function replaceStartStyle($match) { if (!$this->decorated) { return ''; } if (isset(static::$styles[strtolower($match[1])])) { $parameters = static::$styles[strtolower($match[1])]; } else { // bg=blue;fg=red if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($match[1]), $matches, PREG_SET_ORDER)) { throw new \InvalidArgumentException(sprintf('Unknown style "%s".', $match[1])); } $parameters = array(); foreach ($matches as $match) { $parameters[$match[1]] = $match[2]; } } $codes = array(); if (isset($parameters['fg'])) { $codes[] = static::$foreground[$parameters['fg']]; } if (isset($parameters['bg'])) { $codes[] = static::$background[$parameters['bg']]; } foreach (static::$options as $option => $value) { if (isset($parameters[$option]) && $parameters[$option]) { $codes[] = $value; } } return "\033[".implode(';', $codes).'m'; } protected function replaceEndStyle($match) { if (!$this->decorated) { return ''; } return "\033[0m"; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * NullOutput suppresses all output. * * $output = new NullOutput(); * * @author Fabien Potencier */ class NullOutput extends Output { /** * Writes a message to the output. * * @param string $message A message to write to the output * @param Boolean $newline Whether to add a newline or not */ public function doWrite($message, $newline) { } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * A Shell wraps an Application to add shell capabilities to it. * * This class only works with a PHP compiled with readline support * (either --with-readline or --with-libedit) * * @author Fabien Potencier */ class Shell { protected $application; protected $history; protected $output; /** * Constructor. * * If there is no readline support for the current PHP executable * a \RuntimeException exception is thrown. * * @param Application $application An application instance * * @throws \RuntimeException When Readline extension is not enabled */ public function __construct(Application $application) { if (!function_exists('readline')) { throw new \RuntimeException('Unable to start the shell as the Readline extension is not enabled.'); } $this->application = $application; $this->history = getenv('HOME').'/.history_'.$application->getName(); $this->output = new ConsoleOutput(); } /** * Runs the shell. */ public function run() { $this->application->setAutoExit(false); $this->application->setCatchExceptions(true); readline_read_history($this->history); readline_completion_function(array($this, 'autocompleter')); $this->output->writeln($this->getHeader()); while (true) { $command = readline($this->application->getName().' > '); if (false === $command) { $this->output->writeln("\n"); break; } readline_add_history($command); readline_write_history($this->history); if (0 !== $ret = $this->application->run(new StringInput($command), $this->output)) { $this->output->writeln(sprintf('The command terminated with an error status (%s)', $ret)); } } } /** * Tries to return autocompletion for the current entered text. * * @param string $text The last segment of the entered text * @param integer $position The current position */ protected function autocompleter($text, $position) { $info = readline_info(); $text = substr($info['line_buffer'], 0, $info['end']); if ($info['point'] !== $info['end']) { return true; } // task name? if (false === strpos($text, ' ') || !$text) { return array_keys($this->application->getCommands()); } // options and arguments? try { $command = $this->application->findCommand(substr($text, 0, strpos($text, ' '))); } catch (\Exception $e) { return true; } $list = array('--help'); foreach ($command->getDefinition()->getOptions() as $option) { $list[] = '--'.$option->getName(); } return $list; } /** * Returns the shell header. * * @return string The header string */ protected function getHeader() { return <<{$this->application->getName()} shell ({$this->application->getVersion()}). At the prompt, type help for some help, or list to get a list available commands. To exit the shell, type ^D. EOF; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * The Formatter class provides helpers to format messages. * * @author Fabien Potencier */ class FormatterHelper extends Helper { /** * Formats a message within a section. * * @param string $section The section name * @param string $message The message * @param string $style The style to apply to the section */ public function formatSection($section, $message, $style = 'info') { return sprintf('<%s>[%s] %s', $style, $section, $style, $message); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string $style The style to apply to the whole block * @param Boolean $large Whether to return a large block * * @return string The formatter message */ public function formatBlock($messages, $style, $large = false) { if (!is_array($messages)) { $messages = array($messages); } $len = 0; $lines = array(); foreach ($messages as $message) { $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); $len = max($this->strlen($message) + ($large ? 4 : 2), $len); } $messages = $large ? array(str_repeat(' ', $len)) : array(); foreach ($lines as $line) { $messages[] = $line.str_repeat(' ', $len - $this->strlen($line)); } if ($large) { $messages[] = str_repeat(' ', $len); } foreach ($messages as &$message) { $message = sprintf('<%s>%s', $style, $message, $style); } return implode("\n", $messages); } protected function strlen($string) { return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string); } /** * Returns the helper's canonical name */ public function getName() { return 'formatter'; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * HelperSet represents a set of helpers to be used with a command. * * @author Fabien Potencier */ class HelperSet { protected $helpers; protected $command; /** * @param Helper[] $helpers An array of helper. */ public function __construct(array $helpers = array()) { $this->helpers = array(); foreach ($helpers as $alias => $helper) { $this->set($helper, is_int($alias) ? null : $alias); } } /** * Sets a helper. * * @param HelperInterface $value The helper instance * @param string $alias An alias */ public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } /** * Returns true if the helper if defined. * * @param string $name The helper name * * @return Boolean true if the helper is defined, false otherwise */ public function has($name) { return isset($this->helpers[$name]); } /** * Gets a helper value. * * @param string $name The helper name * * @return HelperInterface The helper instance * * @throws \InvalidArgumentException if the helper is not defined */ public function get($name) { if (!$this->has($name)) { throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } return $this->helpers[$name]; } /** * Sets the command associated with this helper set. * * @param Command $command A Command instance */ public function setCommand(Command $command = null) { $this->command = $command; } /** * Gets the command associated with this helper set. * * @return Command A Command instance */ public function getCommand() { return $this->command; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * Helper is the base class for all helper classes. * * @author Fabien Potencier */ abstract class Helper implements HelperInterface { protected $helperSet = null; /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet = null) { $this->helperSet = $helperSet; } /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance */ public function getHelperSet() { return $this->helperSet; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * HelperInterface is the interface all helpers must implement. * * @author Fabien Potencier */ interface HelperInterface { /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance */ function setHelperSet(HelperSet $helperSet = null); /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance */ function getHelperSet(); /** * Returns the canonical name of this helper. * * @return string The canonical name */ function getName(); } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * The Dialog class provides helpers to interact with the user. * * @author Fabien Potencier */ class DialogHelper extends Helper { /** * Asks a question to the user. * * @param OutputInterface $output * @param string|array $question The question to ask * @param string $default The default answer if none is given by the user * * @return string The user answer */ public function ask(OutputInterface $output, $question, $default = null) { // @codeCoverageIgnoreStart $output->writeln($question); $ret = trim(fgets(STDIN)); return $ret ? $ret : $default; // @codeCoverageIgnoreEnd } /** * Asks a confirmation to the user. * * The question will be asked until the user answer by nothing, yes, or no. * * @param OutputInterface $output * @param string|array $question The question to ask * @param Boolean $default The default answer if the user enters nothing * * @return Boolean true if the user has confirmed, false otherwise */ public function askConfirmation(OutputInterface $output, $question, $default = true) { // @codeCoverageIgnoreStart $answer = 'z'; while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) { $answer = $this->ask($output, $question); } if (false === $default) { return $answer && 'y' == strtolower($answer[0]); } else { return !$answer || 'y' == strtolower($answer[0]); } // @codeCoverageIgnoreEnd } /** * Asks for a value and validates the response. * * @param OutputInterface $output * @param string|array $question * @param Closure $validator * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite) * * @return mixed * * @throws \Exception When any of the validator returns an error */ public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false) { // @codeCoverageIgnoreStart $error = null; while (false === $attempts || $attempts--) { if (null !== $error) { $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error')); } $value = $this->ask($output, $question, null); try { return $validator($value); } catch (\Exception $error) { } } throw $error; // @codeCoverageIgnoreEnd } /** * Returns the helper's canonical name */ public function getName() { return 'dialog'; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * An Application is the container for a collection of commands. * * It is the main entry point of a Console application. * * This class is optimized for a standard CLI environment. * * Usage: * * $app = new Application('myapp', '1.0 (stable)'); * $app->addCommand(new SimpleCommand()); * $app->run(); * * @author Fabien Potencier */ class Application { protected $commands; protected $aliases; protected $wantHelps = false; protected $runningCommand; protected $name; protected $version; protected $catchExceptions; protected $autoExit; protected $definition; protected $helperSet; /** * Constructor. * * @param string $name The name of the application * @param string $version The version of the application */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->name = $name; $this->version = $version; $this->catchExceptions = true; $this->autoExit = true; $this->commands = array(); $this->aliases = array(); $this->helperSet = new HelperSet(array( new FormatterHelper(), new DialogHelper(), )); $this->addCommand(new HelpCommand()); $this->addCommand(new ListCommand()); $this->definition = new InputDefinition(array( new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::PARAMETER_NONE, 'Display this help message.'), new InputOption('--quiet', '-q', InputOption::PARAMETER_NONE, 'Do not output any message.'), new InputOption('--verbose', '-v', InputOption::PARAMETER_NONE, 'Increase verbosity of messages.'), new InputOption('--version', '-V', InputOption::PARAMETER_NONE, 'Display this program version.'), new InputOption('--ansi', '-a', InputOption::PARAMETER_NONE, 'Force ANSI output.'), new InputOption('--no-interaction', '-n', InputOption::PARAMETER_NONE, 'Do not ask any interactive question.'), )); } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return integer 0 if everything went fine, or an error code * * @throws \Exception When doRun returns Exception */ public function run(InputInterface $input = null, OutputInterface $output = null) { if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } try { $statusCode = $this->doRun($input, $output); } catch (\Exception $e) { if (!$this->catchExceptions) { throw $e; } $this->renderException($e, $output); $statusCode = $e->getCode(); $statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1; } if ($this->autoExit) { if ($statusCode > 255) { $statusCode = 255; } // @codeCoverageIgnoreStart exit($statusCode); // @codeCoverageIgnoreEnd } else { return $statusCode; } } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return integer 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { $name = $this->getCommandName($input); if (true === $input->hasParameterOption(array('--ansi', '-a'))) { $output->setDecorated(true); } if (true === $input->hasParameterOption(array('--help', '-h'))) { if (!$name) { $name = 'help'; $input = new ArrayInput(array('command' => 'help')); } else { $this->wantHelps = true; } } if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { $input->setInteractive(false); } if (true === $input->hasParameterOption(array('--quiet', '-q'))) { $output->setVerbosity(Output::VERBOSITY_QUIET); } elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) { $output->setVerbosity(Output::VERBOSITY_VERBOSE); } if (true === $input->hasParameterOption(array('--version', '-V'))) { $output->writeln($this->getLongVersion()); return 0; } if (!$name) { $name = 'list'; $input = new ArrayInput(array('command' => 'list')); } // the command name MUST be the first element of the input $command = $this->findCommand($name); $this->runningCommand = $command; $statusCode = $command->run($input, $output); $this->runningCommand = null; return is_numeric($statusCode) ? $statusCode : 0; } /** * Set a helper set to be used with the command. * * @param HelperSet $helperSet The helper set */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Get the helper set associated with the command * * @return HelperSet The HelperSet instance associated with this command */ public function getHelperSet() { return $this->helperSet; } /** * Gets the InputDefinition related to this Application. * * @return InputDefinition The InputDefinition instance */ public function getDefinition() { return $this->definition; } /** * Gets the help message. * * @return string A help message. */ public function getHelp() { $messages = array( $this->getLongVersion(), '', 'Usage:', sprintf(" [options] command [arguments]\n"), 'Options:', ); foreach ($this->definition->getOptions() as $option) { $messages[] = sprintf(' %-29s %s %s', '--'.$option->getName().'', $option->getShortcut() ? '-'.$option->getShortcut().'' : ' ', $option->getDescription() ); } return implode("\n", $messages); } /** * Sets whether to catch exceptions or not during commands execution. * * @param Boolean $boolean Whether to catch exceptions or not during commands execution */ public function setCatchExceptions($boolean) { $this->catchExceptions = (Boolean) $boolean; } /** * Sets whether to automatically exit after a command execution or not. * * @param Boolean $boolean Whether to automatically exit after a command execution or not */ public function setAutoExit($boolean) { $this->autoExit = (Boolean) $boolean; } /** * Gets the name of the application. * * @return string The application name */ public function getName() { return $this->name; } /** * Sets the application name. * * @param string $name The application name */ public function setName($name) { $this->name = $name; } /** * Gets the application version. * * @return string The application version */ public function getVersion() { return $this->version; } /** * Sets the application version. * * @param string $version The application version */ public function setVersion($version) { $this->version = $version; } /** * Returns the long version of the application. * * @return string The long application version */ public function getLongVersion() { if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { return sprintf('%s version %s', $this->getName(), $this->getVersion()); } else { return 'Console Tool'; } } /** * Registers a new command. * * @param string $name The command name * * @return Command The newly created command */ public function register($name) { return $this->addCommand(new Command($name)); } /** * Adds an array of command objects. * * @param Command[] $commands An array of commands */ public function addCommands(array $commands) { foreach ($commands as $command) { $this->addCommand($command); } } /** * Adds a command object. * * If a command with the same name already exists, it will be overridden. * * @param Command $command A Command object * * @return Command The registered command */ public function addCommand(Command $command) { $command->setApplication($this); $this->commands[$command->getFullName()] = $command; foreach ($command->getAliases() as $alias) { $this->aliases[$alias] = $command; } return $command; } /** * Returns a registered command by name or alias. * * @param string $name The command name or alias * * @return Command A Command object * * @throws \InvalidArgumentException When command name given does not exist */ public function getCommand($name) { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); } $command = isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; if ($this->wantHelps) { $this->wantHelps = false; $helpCommand = $this->getCommand('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } /** * Returns true if the command exists, false otherwise * * @param string $name The command name or alias * * @return Boolean true if the command exists, false otherwise */ public function hasCommand($name) { return isset($this->commands[$name]) || isset($this->aliases[$name]); } /** * Returns an array of all unique namespaces used by currently registered commands. * * It does not returns the global namespace which always exists. * * @return array An array of namespaces */ public function getNamespaces() { $namespaces = array(); foreach ($this->commands as $command) { if ($command->getNamespace()) { $namespaces[$command->getNamespace()] = true; } } return array_keys($namespaces); } /** * Finds a registered namespace by a name or an abbreviation. * * @return string A registered namespace * * @throws \InvalidArgumentException When namespace is incorrect or ambiguous */ public function findNamespace($namespace) { $abbrevs = static::getAbbreviations($this->getNamespaces()); if (!isset($abbrevs[$namespace])) { throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace)); } if (count($abbrevs[$namespace]) > 1) { throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace]))); } return $abbrevs[$namespace][0]; } /** * Finds a command by name or alias. * * Contrary to getCommand, this command tries to find the best * match if you give it an abbreviation of a name or alias. * * @param string $name A command name or a command alias * * @return Command A Command instance * * @throws \InvalidArgumentException When command name is incorrect or ambiguous */ public function findCommand($name) { // namespace $namespace = ''; if (false !== $pos = strrpos($name, ':')) { $namespace = $this->findNamespace(substr($name, 0, $pos)); $name = substr($name, $pos + 1); } $fullName = $namespace ? $namespace.':'.$name : $name; // name $commands = array(); foreach ($this->commands as $command) { if ($command->getNamespace() == $namespace) { $commands[] = $command->getName(); } } $abbrevs = static::getAbbreviations($commands); if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name])) { return $this->getCommand($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]); } if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1) { $suggestions = $this->getAbbreviationSuggestions(array_map(function ($command) use ($namespace) { return $namespace.':'.$command; }, $abbrevs[$name])); throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $suggestions)); } // aliases $abbrevs = static::getAbbreviations(array_keys($this->aliases)); if (!isset($abbrevs[$fullName])) { throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $fullName)); } if (count($abbrevs[$fullName]) > 1) { throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $this->getAbbreviationSuggestions($abbrevs[$fullName]))); } return $this->getCommand($abbrevs[$fullName][0]); } /** * Gets the commands (registered in the given namespace if provided). * * The array keys are the full names and the values the command instances. * * @param string $namespace A namespace name * * @return array An array of Command instances */ public function getCommands($namespace = null) { if (null === $namespace) { return $this->commands; } $commands = array(); foreach ($this->commands as $name => $command) { if ($namespace === $command->getNamespace()) { $commands[$name] = $command; } } return $commands; } /** * Returns an array of possible abbreviations given a set of names. * * @param array $names An array of names * * @return array An array of abbreviations */ static public function getAbbreviations($names) { $abbrevs = array(); foreach ($names as $name) { for ($len = strlen($name) - 1; $len > 0; --$len) { $abbrev = substr($name, 0, $len); if (!isset($abbrevs[$abbrev])) { $abbrevs[$abbrev] = array($name); } else { $abbrevs[$abbrev][] = $name; } } } // Non-abbreviations always get entered, even if they aren't unique foreach ($names as $name) { $abbrevs[$name] = array($name); } return $abbrevs; } /** * Returns a text representation of the Application. * * @param string $namespace An optional namespace name * * @return string A string representing the Application */ public function asText($namespace = null) { $commands = $namespace ? $this->getCommands($this->findNamespace($namespace)) : $this->commands; $messages = array($this->getHelp(), ''); if ($namespace) { $messages[] = sprintf("Available commands for the \"%s\" namespace:", $namespace); } else { $messages[] = 'Available commands:'; } $width = 0; foreach ($commands as $command) { $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; } $width += 2; // add commands by namespace foreach ($this->sortCommands($commands) as $space => $commands) { if (!$namespace && '_global' !== $space) { $messages[] = ''.$space.''; } foreach ($commands as $command) { $aliases = $command->getAliases() ? ' ('.implode(', ', $command->getAliases()).')' : ''; $messages[] = sprintf(" %-${width}s %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases); } } return implode("\n", $messages); } /** * Returns an XML representation of the Application. * * @param string $namespace An optional namespace name * @param Boolean $asDom Whether to return a DOM or an XML string * * @return string|DOMDocument An XML string representing the Application */ public function asXml($namespace = null, $asDom = false) { $commands = $namespace ? $this->getCommands($this->findNamespace($namespace)) : $this->commands; $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $dom->appendChild($xml = $dom->createElement('symfony')); $xml->appendChild($commandsXML = $dom->createElement('commands')); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } else { $xml->appendChild($namespacesXML = $dom->createElement('namespaces')); } // add commands by namespace foreach ($this->sortCommands($commands) as $space => $commands) { if (!$namespace) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $space); } foreach ($commands as $command) { if (!$namespace) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($command->getName())); } $node = $command->asXml(true)->getElementsByTagName('command')->item(0); $node = $dom->importNode($node, true); $commandsXML->appendChild($node); } } return $asDom ? $dom : $dom->saveXml(); } /** * Renders a catched exception. * * @param Exception $e An exception instance * @param OutputInterface $output An OutputInterface instance */ public function renderException($e, $output) { $strlen = function ($string) { return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string); }; $title = sprintf(' [%s] ', get_class($e)); $len = $strlen($title); $lines = array(); foreach (explode("\n", $e->getMessage()) as $line) { $lines[] = sprintf(' %s ', $line); $len = max($strlen($line) + 4, $len); } $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title))); foreach ($lines as $line) { $messages[] = $line.str_repeat(' ', $len - $strlen($line)); } $messages[] = str_repeat(' ', $len); $output->writeln("\n"); foreach ($messages as $message) { $output->writeln(''.$message.''); } $output->writeln("\n"); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); $output->writeln("\n"); } if (Output::VERBOSITY_VERBOSE === $output->getVerbosity()) { $output->writeln('Exception trace:'); // exception related properties $trace = $e->getTrace(); array_unshift($trace, array( 'function' => '', 'file' => $e->getFile() != null ? $e->getFile() : 'n/a', 'line' => $e->getLine() != null ? $e->getLine() : 'n/a', 'args' => array(), )); for ($i = 0, $count = count($trace); $i < $count; $i++) { $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; $function = $trace[$i]['function']; $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line)); } $output->writeln("\n"); } } protected function getCommandName(InputInterface $input) { return $input->getFirstArgument('command'); } protected function sortCommands($commands) { $namespacedCommands = array(); foreach ($commands as $name => $command) { $key = $command->getNamespace() ? $command->getNamespace() : '_global'; if (!isset($namespacedCommands[$key])) { $namespacedCommands[$key] = array(); } $namespacedCommands[$key][$name] = $command; } ksort($namespacedCommands); foreach ($namespacedCommands as $name => &$commands) { ksort($commands); } return $namespacedCommands; } protected function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * @author Fabien Potencier */ class ApplicationTester { protected $application; protected $display; protected $input; protected $output; /** * Constructor. * * @param Application $application A Application instance to test. */ public function __construct(Application $application) { $this->application = $application; } /** * Executes the application. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of arguments and options * @param array $options An array of options */ public function run(array $input, $options = array()) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } $ret = $this->application->run($this->input, $this->output); rewind($this->output->getStream()); return $this->display = stream_get_contents($this->output->getStream()); } /** * Gets the display returned by the last execution of the application. * * @return string The display */ public function getDisplay() { return $this->display; } /** * Gets the input instance used by the last execution of the application. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the application. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * @author Fabien Potencier */ class CommandTester { protected $command; protected $display; protected $input; protected $output; /** * Constructor. * * @param Command $command A Command instance to test. */ public function __construct(Command $command) { $this->command = $command; } /** * Executes the command. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of arguments and options * @param array $options An array of options */ public function execute(array $input, array $options = array()) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } $ret = $this->command->run($this->input, $this->output); rewind($this->output->getStream()); return $this->display = stream_get_contents($this->output->getStream()); } /** * Gets the display returned by the last execution of the command. * * @return string The display */ public function getDisplay() { return $this->display; } /** * Gets the input instance used by the last execution of the command. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the command. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * Input is the base class for all concrete Input classes. * * Three concrete classes are provided by default: * * * `ArgvInput`: The input comes from the CLI arguments (argv) * * `StringInput`: The input is provided as a string * * `ArrayInput`: The input is provided as an array * * @author Fabien Potencier */ abstract class Input implements InputInterface { protected $definition; protected $options; protected $arguments; protected $interactive = true; /** * Constructor. * * @param InputDefinition $definition A InputDefinition instance */ public function __construct(InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ public function bind(InputDefinition $definition) { $this->arguments = array(); $this->options = array(); $this->definition = $definition; $this->parse(); } /** * Processes command line arguments. */ abstract protected function parse(); /** * @throws \RuntimeException When not enough arguments are given */ public function validate() { if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { throw new \RuntimeException('Not enough arguments.'); } } public function isInteractive() { return $this->interactive; } public function setInteractive($interactive) { $this->interactive = (Boolean) $interactive; } /** * Returns the argument values. * * @return array An array of argument values */ public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } /** * Returns the argument value for a given argument name. * * @param string $name The argument name * * @return mixed The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function getArgument($name) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); } /** * Sets an argument value by name. * * @param string $name The argument name * @param string $value The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function setArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|integer $name The InputArgument name or position * * @return Boolean true if the InputArgument object exists, false otherwise */ public function hasArgument($name) { return $this->definition->hasArgument($name); } /** * Returns the options values. * * @return array An array of option values */ public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } /** * Returns the option value for a given option name. * * @param string $name The option name * * @return mixed The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function getOption($name) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } /** * Sets an option value by name. * * @param string $name The option name * @param string $value The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function setOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return Boolean true if the InputOption object exists, false otherwise */ public function hasOption($name) { return $this->definition->hasOption($name); } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * ArrayInput represents an input provided as an array. * * Usage: * * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); * * @author Fabien Potencier */ class ArrayInput extends Input { protected $parameters; /** * Constructor. * * @param array $param An array of parameters * @param InputDefinition $definition A InputDefinition instance */ public function __construct(array $parameters, InputDefinition $definition = null) { $this->parameters = $parameters; parent::__construct($definition); } /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument() { foreach ($this->parameters as $key => $value) { if ($key && '-' === $key[0]) { continue; } return $value; } } /** * Returns true if the raw parameters (not parsed) contains a value. * * This method is to be used to introspect the input parameters * before it has been validated. It must be used carefully. * * @param string|array $value The values to look for in the raw parameters (can be an array) * * @return Boolean true if the value is contained in the raw parameters */ public function hasParameterOption($values) { if (!is_array($values)) { $values = array($values); } foreach ($this->parameters as $k => $v) { if (!is_int($k)) { $v = $k; } if (in_array($v, $values)) { return true; } } return false; } /** * Processes command line arguments. */ protected function parse() { foreach ($this->parameters as $key => $value) { if ('--' === substr($key, 0, 2)) { $this->addLongOption(substr($key, 2), $value); } elseif ('-' === $key[0]) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ protected function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws \InvalidArgumentException When option given doesn't exist * @throws \InvalidArgumentException When a required value is missing */ protected function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isParameterRequired()) { throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name)); } $value = $option->isParameterOptional() ? $option->getDefault() : true; } $this->options[$name] = $value; } /** * Adds an argument value. * * @param string $name The argument name * @param mixed $value The value for the argument * * @throws \InvalidArgumentException When argument given doesn't exist */ protected function addArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * Represents a command line option. * * @author Fabien Potencier */ class InputOption { const PARAMETER_NONE = 1; const PARAMETER_REQUIRED = 2; const PARAMETER_OPTIONAL = 4; const PARAMETER_IS_ARRAY = 8; protected $name; protected $shortcut; protected $mode; protected $default; protected $description; /** * Constructor. * * @param string $name The option name * @param string $shortcut The shortcut (can be null) * @param integer $mode The option mode: self::PARAMETER_REQUIRED, self::PARAMETER_NONE or self::PARAMETER_OPTIONAL * @param string $description A description text * @param mixed $default The default value (must be null for self::PARAMETER_REQUIRED or self::PARAMETER_NONE) * * @throws \InvalidArgumentException If option mode is invalid or incompatible */ public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) { if ('--' === substr($name, 0, 2)) { $name = substr($name, 2); } if (empty($shortcut)) { $shortcut = null; } if (null !== $shortcut) { if ('-' === $shortcut[0]) { $shortcut = substr($shortcut, 1); } } if (null === $mode) { $mode = self::PARAMETER_NONE; } else if (!is_int($mode) || $mode > 15) { throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptParameter()) { throw new \InvalidArgumentException('Impossible to have an option mode PARAMETER_IS_ARRAY if the option does not accept a parameter.'); } $this->setDefault($default); } /** * Returns the shortcut. * * @return string The shortcut */ public function getShortcut() { return $this->shortcut; } /** * Returns the name. * * @return string The name */ public function getName() { return $this->name; } /** * Returns true if the option accept a parameter. * * @return Boolean true if parameter mode is not self::PARAMETER_NONE, false otherwise */ public function acceptParameter() { return $this->isParameterRequired() || $this->isParameterOptional(); } /** * Returns true if the option requires a parameter. * * @return Boolean true if parameter mode is self::PARAMETER_REQUIRED, false otherwise */ public function isParameterRequired() { return self::PARAMETER_REQUIRED === (self::PARAMETER_REQUIRED & $this->mode); } /** * Returns true if the option takes an optional parameter. * * @return Boolean true if parameter mode is self::PARAMETER_OPTIONAL, false otherwise */ public function isParameterOptional() { return self::PARAMETER_OPTIONAL === (self::PARAMETER_OPTIONAL & $this->mode); } /** * Returns true if the option can take multiple values. * * @return Boolean true if mode is self::PARAMETER_IS_ARRAY, false otherwise */ public function isArray() { return self::PARAMETER_IS_ARRAY === (self::PARAMETER_IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value */ public function setDefault($default = null) { if (self::PARAMETER_NONE === (self::PARAMETER_NONE & $this->mode) && null !== $default) { throw new \LogicException('Cannot set a default value when using Option::PARAMETER_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new \LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptParameter() ? $default : false; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * A InputDefinition represents a set of valid command line arguments and options. * * Usage: * * $definition = new InputDefinition(array( * new InputArgument('name', InputArgument::REQUIRED), * new InputOption('foo', 'f', InputOption::PARAMETER_REQUIRED), * )); * * @author Fabien Potencier */ class InputDefinition { protected $arguments; protected $requiredCount; protected $hasAnArrayArgument = false; protected $hasOptional; protected $options; protected $shortcuts; /** * Constructor. * * @param array $definition An array of InputArgument and InputOption instance */ public function __construct(array $definition = array()) { $this->setDefinition($definition); } public function setDefinition(array $definition) { $arguments = array(); $options = array(); foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } /** * Sets the InputArgument objects. * * @param array $arguments An array of InputArgument objects */ public function setArguments($arguments = array()) { $this->arguments = array(); $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } /** * Add an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects */ public function addArguments($arguments = array()) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } /** * Add an InputArgument object. * * @param InputArgument $argument An InputArgument object * * @throws \LogicException When incorrect argument is given */ public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new \LogicException(sprintf('An argument with name "%s" already exist.', $argument->getName())); } if ($this->hasAnArrayArgument) { throw new \LogicException('Cannot add an argument after an array argument.'); } if ($argument->isRequired() && $this->hasOptional) { throw new \LogicException('Cannot add a required argument after an optional one.'); } if ($argument->isArray()) { $this->hasAnArrayArgument = true; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->hasOptional = true; } $this->arguments[$argument->getName()] = $argument; } /** * Returns an InputArgument by name or by position. * * @param string|integer $name The InputArgument name or position * * @return InputArgument An InputArgument object * * @throws \InvalidArgumentException When argument given doesn't exist */ public function getArgument($name) { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; if (!$this->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return $arguments[$name]; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|integer $name The InputArgument name or position * * @return Boolean true if the InputArgument object exists, false otherwise */ public function hasArgument($name) { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } /** * Gets the array of InputArgument objects. * * @return array An array of InputArgument objects */ public function getArguments() { return $this->arguments; } /** * Returns the number of InputArguments. * * @return integer The number of InputArguments */ public function getArgumentCount() { return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); } /** * Returns the number of required InputArguments. * * @return integer The number of required InputArguments */ public function getArgumentRequiredCount() { return $this->requiredCount; } /** * Gets the default values. * * @return array An array of default values */ public function getArgumentDefaults() { $values = array(); foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } /** * Sets the InputOption objects. * * @param array $options An array of InputOption objects */ public function setOptions($options = array()) { $this->options = array(); $this->shortcuts = array(); $this->addOptions($options); } /** * Add an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects */ public function addOptions($options = array()) { foreach ($options as $option) { $this->addOption($option); } } /** * Add an InputOption object. * * @param InputOption $option An InputOption object * * @throws \LogicException When option given already exist */ public function addOption(InputOption $option) { if (isset($this->options[$option->getName()])) { throw new \LogicException(sprintf('An option named "%s" already exist.', $option->getName())); } else if (isset($this->shortcuts[$option->getShortcut()])) { throw new \LogicException(sprintf('An option with shortcut "%s" already exist.', $option->getShortcut())); } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { $this->shortcuts[$option->getShortcut()] = $option->getName(); } } /** * Returns an InputOption by name. * * @param string $name The InputOption name * * @return InputOption A InputOption object */ public function getOption($name) { if (!$this->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return Boolean true if the InputOption object exists, false otherwise */ public function hasOption($name) { return isset($this->options[$name]); } /** * Gets the array of InputOption objects. * * @return array An array of InputOption objects */ public function getOptions() { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. * * @param string $name The InputOption shortcut * * @return Boolean true if the InputOption object exists, false otherwise */ public function hasShortcut($name) { return isset($this->shortcuts[$name]); } /** * Gets an InputOption by shortcut. * * @return InputOption An InputOption object */ public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } /** * Gets an array of default values. * * @return array An array of all default values */ public function getOptionDefaults() { $values = array(); foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } /** * Returns the InputOption name given a shortcut. * * @param string $shortcut The shortcut * * @return string The InputOption name * * @throws \InvalidArgumentException When option given does not exist */ protected function shortcutToName($shortcut) { if (!isset($this->shortcuts[$shortcut])) { throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } /** * Gets the synopsis. * * @return string The synopsis */ public function getSynopsis() { $elements = array(); foreach ($this->getOptions() as $option) { $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $elements[] = sprintf('['.($option->isParameterRequired() ? '%s--%s="..."' : ($option->isParameterOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName()); } foreach ($this->getArguments() as $argument) { $elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : '')); if ($argument->isArray()) { $elements[] = sprintf('... [%sN]', $argument->getName()); } } return implode(' ', $elements); } /** * Returns a textual representation of the InputDefinition. * * @return string A string representing the InputDefinition */ public function asText() { // find the largest option or argument name $max = 0; foreach ($this->getOptions() as $option) { $max = strlen($option->getName()) + 2 > $max ? strlen($option->getName()) + 2 : $max; } foreach ($this->getArguments() as $argument) { $max = strlen($argument->getName()) > $max ? strlen($argument->getName()) : $max; } ++$max; $text = array(); if ($this->getArguments()) { $text[] = 'Arguments:'; foreach ($this->getArguments() as $argument) { if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { $default = sprintf(' (default: %s)', is_array($argument->getDefault()) ? str_replace("\n", '', var_export($argument->getDefault(), true)): $argument->getDefault()); } else { $default = ''; } $text[] = sprintf(" %-${max}s %s%s", $argument->getName(), $argument->getDescription(), $default); } $text[] = ''; } if ($this->getOptions()) { $text[] = 'Options:'; foreach ($this->getOptions() as $option) { if ($option->acceptParameter() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { $default = sprintf(' (default: %s)', is_array($option->getDefault()) ? str_replace("\n", '', print_r($option->getDefault(), true)): $option->getDefault()); } else { $default = ''; } $multiple = $option->isArray() ? ' (multiple values allowed)' : ''; $text[] = sprintf(' %-'.$max.'s %s%s%s%s', '--'.$option->getName().'', $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', $option->getDescription(), $default, $multiple); } $text[] = ''; } return implode("\n", $text); } /** * Returns an XML representation of the InputDefinition. * * @param Boolean $asDom Whether to return a DOM or an XML string * * @return string|DOMDocument An XML string representing the InputDefinition */ public function asXml($asDom = false) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $dom->appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($this->getArguments() as $argument) { $argumentsXML->appendChild($argumentXML = $dom->createElement('argument')); $argumentXML->setAttribute('name', $argument->getName()); $argumentXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $argumentXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $argumentXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $argumentXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : ($argument->getDefault() ? array($argument->getDefault()) : array()); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($this->getOptions() as $option) { $optionsXML->appendChild($optionXML = $dom->createElement('option')); $optionXML->setAttribute('name', '--'.$option->getName()); $optionXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); $optionXML->setAttribute('accept_parameter', $option->acceptParameter() ? 1 : 0); $optionXML->setAttribute('is_parameter_required', $option->isParameterRequired() ? 1 : 0); $optionXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $optionXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptParameter()) { $optionXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = is_array($option->getDefault()) ? $option->getDefault() : ($option->getDefault() ? array($option->getDefault()) : array()); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } return $asDom ? $dom : $dom->saveXml(); } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ function getFirstArgument(); /** * Returns true if the raw parameters (not parsed) contains a value. * * This method is to be used to introspect the input parameters * before it has been validated. It must be used carefully. * * @param string $value The value to look for in the raw parameters * * @return Boolean true if the value is contained in the raw parameters */ function hasParameterOption($value); /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ function bind(InputDefinition $definition); function validate(); function getArguments(); function getArgument($name); function getOptions(); function getOption($name); } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * ArgvInput represents an input coming from the CLI arguments. * * Usage: * * $input = new ArgvInput(); * * By default, the `$_SERVER['argv']` array is used for the input values. * * This can be overridden by explicitly passing the input values in the constructor: * * $input = new ArgvInput($_SERVER['argv']); * * If you pass it yourself, don't forget that the first element of the array * is the name of the running program. * * When passing an argument to the constructor, be sure that it respects * the same rules as the argv one. It's almost always better to use the * `StringInput` when you want to provide your own input. * * @author Fabien Potencier * * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 */ class ArgvInput extends Input { protected $tokens; protected $parsed; /** * Constructor. * * @param array $argv An array of parameters from the CLI (in the argv format) * @param InputDefinition $definition A InputDefinition instance */ public function __construct(array $argv = null, InputDefinition $definition = null) { if (null === $argv) { $argv = $_SERVER['argv']; } // strip the program name array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } /** * Processes command line arguments. */ protected function parse() { $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { if ('--' === substr($token, 0, 2)) { $this->parseLongOption($token); } elseif ('-' === $token[0]) { $this->parseShortOption($token); } else { $this->parseArgument($token); } } } /** * Parses a short option. * * @param string $token The current token. */ protected function parseShortOption($token) { $name = substr($token, 1); if (strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptParameter()) { // an option with a value (with no space) $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } /** * Parses a short option set. * * @param string $token The current token * * @throws \RuntimeException When option given doesn't exist */ protected function parseShortOptionSet($name) { $len = strlen($name); for ($i = 0; $i < $len; $i++) { if (!$this->definition->hasShortcut($name[$i])) { throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptParameter()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), true); } } } /** * Parses a long option. * * @param string $token The current token */ protected function parseLongOption($token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); } else { $this->addLongOption($name, null); } } /** * Parses an argument. * * @param string $token The current token * * @throws \RuntimeException When too many arguments are given */ protected function parseArgument($token) { if (!$this->definition->hasArgument(count($this->arguments))) { throw new \RuntimeException('Too many arguments.'); } $this->arguments[$this->definition->getArgument(count($this->arguments))->getName()] = $token; } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ protected function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ protected function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null === $value && $option->acceptParameter()) { // if option accepts an optional or mandatory argument // let's see if there is one provided $next = array_shift($this->parsed); if ('-' !== $next[0]) { $value = $next; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isParameterRequired()) { throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } $value = $option->isParameterOptional() ? $option->getDefault() : true; } $this->options[$name] = $value; } /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument() { foreach ($this->tokens as $token) { if ($token && '-' === $token[0]) { continue; } return $token; } } /** * Returns true if the raw parameters (not parsed) contains a value. * * This method is to be used to introspect the input parameters * before it has been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * * @return Boolean true if the value is contained in the raw parameters */ public function hasParameterOption($values) { if (!is_array($values)) { $values = array($values); } foreach ($this->tokens as $v) { if (in_array($v, $values)) { return true; } } return false; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * StringInput represents an input provided as a string. * * Usage: * * $input = new StringInput('foo --bar="foobar"'); * * @author Fabien Potencier */ class StringInput extends ArgvInput { const REGEX_STRING = '([^ ]+?)(?: |(?tokens = $this->tokenize($input); } /** * @throws \InvalidArgumentException When unable to parse input (should never happen) */ protected function tokenize($input) { $input = preg_replace('/(\r\n|\r|\n|\t)/', ' ', $input); $tokens = array(); $length = strlen($input); $cursor = 0; while ($cursor < $length) { if (preg_match('/\s+/A', $input, $match, null, $cursor)) { } elseif (preg_match('/([^="\' ]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes($match[1]); } else { // should never happen // @codeCoverageIgnoreStart throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); // @codeCoverageIgnoreEnd } $cursor += strlen($match[0]); } return $tokens; } } * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ /** * Represents a command line argument. * * @author Fabien Potencier */ class InputArgument { const REQUIRED = 1; const OPTIONAL = 2; const IS_ARRAY = 4; protected $name; protected $mode; protected $default; protected $description; /** * Constructor. * * @param string $name The argument name * @param integer $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for self::OPTIONAL mode only) * * @throws \InvalidArgumentException When argument mode is not valid */ public function __construct($name, $mode = null, $description = '', $default = null) { if (null === $mode) { $mode = self::OPTIONAL; } else if (is_string($mode) || $mode > 7) { throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } /** * Returns the argument name. * * @return string The argument name */ public function getName() { return $this->name; } /** * Returns true if the argument is required. * * @return Boolean true if parameter mode is self::REQUIRED, false otherwise */ public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } /** * Returns true if the argument can take multiple values. * * @return Boolean true if mode is self::IS_ARRAY, false otherwise */ public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws \LogicException When incorrect default value is given */ public function setDefault($default = null) { if (self::REQUIRED === $this->mode && null !== $default) { throw new \LogicException('Cannot set a default value except for Parameter::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } else if (!is_array($default)) { throw new \LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } } esyepC p͸<)GBMB