From 38e7f63a62040989bb68d6171dd239ff5355a5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Tue, 15 Oct 2013 22:48:33 +0100 Subject: [PATCH] Started to work on Propel2 commands integration (only a few work at the moment) --- Command/AbstractCommand.php | 172 ++++++++++++++++ Command/MigrationDownCommand.php | 58 ++++++ Command/MigrationStatusCommand.php | 58 ++++++ Command/MigrationUpCommand.php | 58 ++++++ Command/SqlInsertCommand.php | 48 +++++ DependencyInjection/Configuration.php | 252 ++++++++++++++++++++++++ DependencyInjection/PropelExtension.php | 141 +++++++++++++ PropelBundle.php | 27 +++ Resources/config/propel.xml | 10 + 9 files changed, 824 insertions(+) create mode 100644 Command/AbstractCommand.php create mode 100644 Command/MigrationDownCommand.php create mode 100644 Command/MigrationStatusCommand.php create mode 100644 Command/MigrationUpCommand.php create mode 100644 Command/SqlInsertCommand.php create mode 100644 DependencyInjection/Configuration.php create mode 100644 DependencyInjection/PropelExtension.php create mode 100644 Resources/config/propel.xml diff --git a/Command/AbstractCommand.php b/Command/AbstractCommand.php new file mode 100644 index 0000000..0f769ba --- /dev/null +++ b/Command/AbstractCommand.php @@ -0,0 +1,172 @@ + + */ +abstract class AbstractCommand extends ContainerAwareCommand +{ + /** + * @var string + */ + protected $cacheDir = null; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->addOption('platform', null, InputOption::VALUE_REQUIRED, 'The platform', BaseCommand::DEFAULT_PLATFORM) + ; + } + + protected function runCommand(Command $command, array $parameters, InputInterface $input, OutputInterface $output) + { + array_unshift($parameters, $this->getName()); + $parameters = array_merge(array( + '--input-dir' => $this->cacheDir, + '--verbose' => $input->getOption('verbose'), + ), $parameters); + + if ($input->hasOption('platform')) { + $parameters['--platform'] = $input->getOption('platform'); + } + + var_dump($parameters); + + $commandInput = new ArrayInput($parameters); + + $command->setApplication($this->getApplication()); + + return $command->run($commandInput, $output); + } + + protected function setupBuildTimeFiles() + { + $kernel = $this->getApplication()->getKernel(); + $this->cacheDir = $kernel->getCacheDir().'/propel'; + + $fs = new Filesystem(); + $fs->mkdir($this->cacheDir); + + // collect all schemas + //$this->copySchemas($kernel, $this->cacheDir); + + // build.properties + $this->createBuildPropertiesFile($kernel, $this->cacheDir.'/build.properties'); + + // buildtime-conf.xml + $this->createBuildTimeFile($this->cacheDir.'/buildtime-conf.xml'); + } + + protected function getConnections(array $connections) + { + $knownConnections = $this->getContainer()->getParameter('propel.configuration'); + + $dsnList = array(); + foreach ($connections as $connection) { + if (!isset($knownConnections[$connection])) { + throw new \InvalidArgumentException(sprintf('Unknown connection "%s"', $connection)); + } + + $dsnList[] = $this->buildDsn($connection, $knownConnections[$connection]['connection']); + } + + return $dsnList; + } + + protected function buildDsn($connectionName, array $connectionData) + { + return sprintf('%s=%s;user=%s;password=%s', $connectionName, $connectionData['dsn'], $connectionData['user'], $connectionData['password']); + } + + /** + * Create a 'build.properties' file. + * + * @param KernelInterface $kernel The application kernel. + * @param string $file Should be 'build.properties'. + */ + protected function createBuildPropertiesFile(KernelInterface $kernel, $file) + { + $fs = new Filesystem(); + $buildPropertiesFile = $kernel->getRootDir().'/config/propel.ini'; + + if ($fs->exists($file)) { + $fs->copy($buildPropertiesFile, $file); + } else { + $fs->touch($file); + } + } + + /** + * Create an XML file which represents propel.configuration + * + * @param string $file Should be 'buildtime-conf.xml'. + */ + protected function createBuildTimeFile($file) + { + if (!$this->getContainer()->hasParameter('propel.configuration')) { + throw new \InvalidArgumentException('Could not find Propel configuration.'); + } + + $xml = strtr(<< + + + + +EOT + , array('%default_connection%' => $this->getContainer()->getParameter('propel.dbal.default_connection'))); + + $datasources = $this->getContainer()->getParameter('propel.configuration'); + foreach ($datasources as $name => $datasource) { + $xml .= strtr(<< +%adapter% + +%dsn% +%username% +%password% + + + +EOT + , array( + '%name%' => $name, + '%adapter%' => $datasource['adapter'], + '%dsn%' => $datasource['connection']['dsn'], + '%username%' => $datasource['connection']['user'], + '%password%' => isset($datasource['connection']['password']) ? $datasource['connection']['password'] : '', + )); + } + + $xml .= << + + +EOT; + + file_put_contents($file, $xml); + } +} diff --git a/Command/MigrationDownCommand.php b/Command/MigrationDownCommand.php new file mode 100644 index 0000000..d05f54e --- /dev/null +++ b/Command/MigrationDownCommand.php @@ -0,0 +1,58 @@ + + */ +class MigrationDownCommand extends AbstractCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + parent::configure(); + + $this + ->setName('propel:migration:down') + ->setDescription('Execute migrations down') + + ->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore') + ->addOption('migration-table', null, InputOption::VALUE_REQUIRED, 'Migration table name', BaseCommand::DEFAULT_MIGRATION_TABLE) + ->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations'; + + $this->setupBuildTimeFiles(); + + $params = array( + '--connection' => $this->getConnections($input->getOption('connection')), + '--migration-table' => $input->getOption('migration-table'), + '--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir, + ); + + return $this->runCommand(new BaseCommand(), $params, $input, $output); + } +} diff --git a/Command/MigrationStatusCommand.php b/Command/MigrationStatusCommand.php new file mode 100644 index 0000000..4563e5e --- /dev/null +++ b/Command/MigrationStatusCommand.php @@ -0,0 +1,58 @@ + + */ +class MigrationStatusCommand extends AbstractCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + parent::configure(); + + $this + ->setName('propel:migration:status') + ->setDescription('Get migration status') + + ->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore') + ->addOption('migration-table', null, InputOption::VALUE_REQUIRED, 'Migration table name', BaseCommand::DEFAULT_MIGRATION_TABLE) + ->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations'; + + $this->setupBuildTimeFiles(); + + $params = array( + '--connection' => $this->getConnections($input->getOption('connection')), + '--migration-table' => $input->getOption('migration-table'), + '--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir, + ); + + return $this->runCommand(new BaseCommand(), $params, $input, $output); + } +} diff --git a/Command/MigrationUpCommand.php b/Command/MigrationUpCommand.php new file mode 100644 index 0000000..67732c6 --- /dev/null +++ b/Command/MigrationUpCommand.php @@ -0,0 +1,58 @@ + + */ +class MigrationUpCommand extends AbstractCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + parent::configure(); + + $this + ->setName('propel:migration:up') + ->setDescription('Execute migrations up') + + ->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore') + ->addOption('migration-table', null, InputOption::VALUE_REQUIRED, 'Migration table name', BaseCommand::DEFAULT_MIGRATION_TABLE) + ->addOption('output-dir', null, InputOption::VALUE_OPTIONAL, 'The output directory') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $defaultOutputDir = $this->getApplication()->getKernel()->getRootDir().'/propel/migrations'; + + $this->setupBuildTimeFiles(); + + $params = array( + '--connection' => $this->getConnections($input->getOption('connection')), + '--migration-table' => $input->getOption('migration-table'), + '--output-dir' => $input->getOption('output-dir') ?: $defaultOutputDir, + ); + + return $this->runCommand(new BaseCommand(), $params, $input, $output); + } +} diff --git a/Command/SqlInsertCommand.php b/Command/SqlInsertCommand.php new file mode 100644 index 0000000..0844dde --- /dev/null +++ b/Command/SqlInsertCommand.php @@ -0,0 +1,48 @@ + + */ +class SqlInsertCommand extends AbstractCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('propel:sql:insert') + ->setDescription('Insert SQL statements') + ->addOption('connection', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Connection to use. Example: default, bookstore') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->setupBuildTimeFiles(); + + $params = array( + '--connection' => $this->getConnections($input->getOption('connection')), + ); + $command = new \Propel\Generator\Command\SqlInsertCommand(); + + return $this->runCommand($command, $params, $input, $output); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php new file mode 100644 index 0000000..8a594cb --- /dev/null +++ b/DependencyInjection/Configuration.php @@ -0,0 +1,252 @@ + + */ +class Configuration implements ConfigurationInterface +{ + private $debug; + + /** + * Constructor + * + * @param Boolean $debug Wether to use the debug mode + */ + public function __construct($debug) + { + $this->debug = (Boolean) $debug; + } + + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('propel'); + + $this->addGeneralSection($rootNode); + $this->addDbalSection($rootNode); + + return $treeBuilder; + } + + /** + * Adds 'general' configuration. + * + * propel: + * logging: %kernel.debug% + * build_properties: + * xxxx.xxxx: xxxxxx + * ... + * behaviors: + * fooable: My\FooableBehavior + * barable: src.barable.BarableBehavior + */ + private function addGeneralSection(ArrayNodeDefinition $node) + { + $node + ->children() + ->scalarNode('logging')->defaultValue($this->debug)->end() + ->arrayNode('build_properties') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->arrayNode('behaviors') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ; + } + + /** + * Adds 'dbal' configuration. + * + * propel: + * dbal: + * driver: mysql + * user: root + * password: null + * dsn: xxxxxxxx + * options: {} + * attributes: {} + * settings: {} + * default_connection: xxxxxx + */ + private function addDbalSection(ArrayNodeDefinition $node) + { + $node + ->children() + ->arrayNode('dbal') + ->beforeNormalization() + ->ifNull() + ->then(function ($v) { return array ('connections' => array('default' => array())); }) + ->end() + ->children() + ->scalarNode('default_connection')->defaultValue('default')->end() + ->scalarNode('driver') + ->beforeNormalization() + ->always() + ->then(function ($v) { return str_replace('pdo_', '', $v); }) + ->end() + ->defaultValue('mysql') + ->end() + ->scalarNode('user')->defaultValue('root')->end() + ->scalarNode('password')->defaultValue('')->end() + ->scalarNode('dsn') + ->beforeNormalization() + ->always() + ->then(function ($v) { return str_replace('pdo_', '', $v); }) + ->end() + ->defaultValue('') + ->end() + ->scalarNode('classname')->defaultValue($this->debug ? 'DebugPDO' : 'PropelPDO')->end() + ->end() + ->fixXmlConfig('option') + ->children() + ->arrayNode('options') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('attribute') + ->children() + ->arrayNode('attributes') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('setting') + ->children() + ->arrayNode('settings') + ->useAttributeAsKey('key') + ->prototype('array') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('connection') + ->append($this->getDbalConnectionsNode()) + ->end() + ; + } + + /** + * Returns a tree configuration for this part of configuration: + * + * connections: + * default: + * driver: mysql + * user: root + * password: null + * dsn: xxxxxxxx + * classname: PropelPDO + * options: {} + * attributes: {} + * settings: {} + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + private function getDbalConnectionsNode() + { + $treeBuilder = new TreeBuilder(); + $node = $treeBuilder->root('connections'); + + $node + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('driver') + ->beforeNormalization() + ->always() + ->then(function ($v) { return str_replace('pdo_', '', $v); }) + ->end() + ->defaultValue('mysql') + ->end() + ->scalarNode('user')->defaultValue('root')->end() + ->scalarNode('password')->defaultValue('')->end() + ->scalarNode('dsn') + ->beforeNormalization() + ->always() + ->then(function ($v) { return str_replace('pdo_', '', $v); }) + ->end() + ->defaultValue('') + ->end() + ->scalarNode('classname')->defaultValue($this->debug ? 'DebugPDO' : 'PropelPDO')->end() + ->arrayNode('slaves') + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('driver') + ->beforeNormalization() + ->always() + ->then(function ($v) { return str_replace('pdo_', '', $v); }) + ->end() + ->defaultValue('mysql') + ->end() + ->scalarNode('user')->defaultValue('root')->end() + ->scalarNode('password')->defaultValue('')->end() + ->scalarNode('dsn') + ->beforeNormalization() + ->always() + ->then(function ($v) { return str_replace('pdo_', '', $v); }) + ->end() + ->defaultValue('') + ->end() + ->scalarNode('classname')->defaultValue($this->debug ? 'DebugPDO' : 'PropelPDO')->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('option') + ->children() + ->arrayNode('options') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('attribute') + ->children() + ->arrayNode('attributes') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('setting') + ->children() + ->arrayNode('settings') + ->useAttributeAsKey('key') + ->prototype('array') + ->useAttributeAsKey('key') + ->prototype('scalar') + ->end() + ->end() + ->end() + ; + + return $node; + } +} diff --git a/DependencyInjection/PropelExtension.php b/DependencyInjection/PropelExtension.php new file mode 100644 index 0000000..84b0a51 --- /dev/null +++ b/DependencyInjection/PropelExtension.php @@ -0,0 +1,141 @@ + + */ +class PropelExtension extends Extension +{ + /** + * Loads the Propel configuration. + * + * @param array $configs An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function load(array $configs, ContainerBuilder $container) + { + $processor = new Processor(); + $configuration = $this->getConfiguration($configs, $container); + $config = $processor->processConfiguration($configuration, $configs); + + if (isset($config['logging']) && $config['logging']) { + $logging = $config['logging']; + } else { + $logging = false; + } + + $container->setParameter('propel.logging', $logging); + + // Load services + if (!$container->hasDefinition('propel')) { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('propel.xml'); + //$loader->load('converters.xml'); + } + + // build properties + if (isset($config['build_properties']) && is_array($config['build_properties'])) { + $buildProperties = $config['build_properties']; + } else { + $buildProperties = array(); + } + + // behaviors + if (isset($config['behaviors']) && is_array($config['behaviors'])) { + foreach ($config['behaviors'] as $name => $class) { + $buildProperties[sprintf('propel.behavior.%s.class', $name)] = $class; + } + } + + if (!empty($config['dbal'])) { + $this->dbalLoad($config['dbal'], $container); + } + } + + /** + * Loads the DBAL configuration. + * + * @param array $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function dbalLoad(array $config, ContainerBuilder $container) + { + if (empty($config['default_connection'])) { + $keys = array_keys($config['connections']); + $config['default_connection'] = reset($keys); + } + + $connectionName = $config['default_connection']; + $container->setParameter('propel.dbal.default_connection', $connectionName); + + if (0 === count($config['connections'])) { + $config['connections'] = array($connectionName => $config); + } + + $c = array(); + foreach ($config['connections'] as $name => $conf) { + $c[$name]['adapter'] = $conf['driver']; + if (!empty($conf['slaves'])) { + $c[$name]['slaves']['connection'] = $conf['slaves']; + } + + foreach (array('dsn', 'user', 'password', 'classname', 'options', 'attributes', 'settings') as $att) { + if (isset($conf[$att])) { + $c[$name]['connection'][$att] = $conf[$att]; + } + } + } + + // Alias the default connection if not defined + if (!isset($c['default'])) { + $c['default'] = $c[$connectionName]; + } + + $container->setParameter('propel.configuration', $c); + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new Configuration($container->getParameter('kernel.debug')); + } + + /** + * Returns the base path for the XSD files. + * + * @return string The XSD base path + */ + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config/schema'; + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'propel'; + } +} diff --git a/PropelBundle.php b/PropelBundle.php index 71641e1..2e8ecc9 100644 --- a/PropelBundle.php +++ b/PropelBundle.php @@ -10,6 +10,9 @@ namespace Propel\PropelBundle; +use Propel\Runtime\Propel; +use Propel\Runtime\Connection\ConnectionManagerSingle; + use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -20,11 +23,35 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class PropelBundle extends Bundle { + /** + * {@inheritdoc} + */ public function boot() { + $this->configureConnections(); } + /** + * {@inheritdoc} + */ public function build(ContainerBuilder $container) { } + + protected function configureConnections() + { + $connections_config = $this->container->getParameter('propel.configuration'); + $default_datasource = $this->container->getParameter('propel.dbal.default_connection'); + + $serviceContainer = Propel::getServiceContainer(); + $serviceContainer->setDefaultDatasource($default_datasource); + + foreach ($connections_config as $name => $config) { + $manager = new ConnectionManagerSingle(); + $manager->setConfiguration($config['connection']); + + $serviceContainer->setAdapterClass($name, $config['adapter']); + $serviceContainer->setConnectionManager($name, $manager); + } + } } diff --git a/Resources/config/propel.xml b/Resources/config/propel.xml new file mode 100644 index 0000000..fac3889 --- /dev/null +++ b/Resources/config/propel.xml @@ -0,0 +1,10 @@ + + + + + + default + +