diff --git a/Command/AbstractPropelCommand.php b/Command/AbstractPropelCommand.php index 314844f..18d3e6c 100644 --- a/Command/AbstractPropelCommand.php +++ b/Command/AbstractPropelCommand.php @@ -235,15 +235,25 @@ abstract class AbstractPropelCommand extends ContainerAwareCommand { $finalSchemas = array(); foreach ($kernel->getBundles() as $bundle) { - if (is_dir($dir = $bundle->getPath().'/Resources/config')) { - $finder = new Finder(); - $schemas = $finder->files()->name('*schema.xml')->followLinks()->in($dir); + $finalSchemas = array_merge($finalSchemas, $this->getSchemasFromBundle($bundle)); + } - if (!iterator_count($schemas)) { - continue; - } + return $finalSchemas; + } + + /** + * @param \Symfony\Component\HttpKernel\Bundle\BundleInterface + */ + protected function getSchemasFromBundle(BundleInterface $bundle) + { + $finalSchemas = array(); + + if (is_dir($dir = $bundle->getPath().'/Resources/config')) { + $finder = new Finder(); + $schemas = $finder->files()->name('*schema.xml')->followLinks()->in($dir); + + if (iterator_count($schemas)) { foreach ($schemas as $schema) { - $logicalName = $this->transformToLogicalName($schema, $bundle); $finalSchema = new \SplFileInfo($this->getFileLocator()->locate($logicalName)); @@ -255,6 +265,11 @@ abstract class AbstractPropelCommand extends ContainerAwareCommand return $finalSchemas; } + protected function getRelativeFileName(\SplFileInfo $file) + { + return substr(str_replace(realpath($this->getContainer()->getParameter('kernel.root_dir') . '/../'), '', $file), 1); + } + /** * Create a 'build.properties' file. * @@ -424,7 +439,7 @@ EOT; * Write Propel output as summary based on a Regexp. * * @param OutputInterface $output The output object. - * @param string $taskname A task name + * @param string $taskname A task name */ protected function writeSummary(OutputInterface $output, $taskname) { @@ -447,8 +462,8 @@ EOT; * @see https://github.com/sensio/SensioGeneratorBundle/blob/master/Command/Helper/DialogHelper.php#L52 * * @param OutputInterface $output The output. - * @param string $text A text message. - * @param string $style A style to apply on the section. + * @param string $text A text message. + * @param string $style A style to apply on the section. */ protected function writeSection(OutputInterface $output, $text, $style = 'bg=blue;fg=white') { @@ -463,8 +478,8 @@ EOT; * Renders an error message if a task has failed. * * @param OutputInterface $output The output. - * @param string $taskName A task name. - * @param Boolean $more Whether to add a 'more details' message or not. + * @param string $taskName A task name. + * @param Boolean $more Whether to add a 'more details' message or not. */ protected function writeTaskError($output, $taskName, $more = true) { @@ -479,25 +494,42 @@ EOT; /** * @param OutputInterface $output The output. - * @param string $filename The filename. + * @param string $filename The filename. */ protected function writeNewFile($output, $filename) { return $output->writeln('>> File+ ' . $filename); } + /** + * @param OutputInterface $output The output. + * @param string $directory The directory. + */ + protected function writeNewDirectory($output, $directory) + { + return $output->writeln('>> Dir+ ' . $directory); + } + /** * Ask confirmation from the user. * * @param OutputInterface $output The output. - * @param string $question A given question. - * @param string $default A default response. + * @param string $question A given question. + * @param string $default A default response. */ protected function askConfirmation(OutputInterface $output, $question, $default = null) { return $this->getHelperSet()->get('dialog')->askConfirmation($output, $question, $default); } + /** + * @return \Symfony\Component\Config\FileLocatorInterface + */ + protected function getFileLocator() + { + return $this->getContainer()->get('file_locator'); + } + private function transformToLogicalName(\SplFileInfo $schema, BundleInterface $bundle) { $schemaPath = str_replace($bundle->getPath(). DIRECTORY_SEPARATOR . 'Resources' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR, '', $schema->getRealPath()); @@ -505,11 +537,6 @@ EOT; return sprintf('@%s/Resources/config/%s', $bundle->getName(), $schemaPath); } - protected function getFileLocator() - { - return $this->getContainer()->get('file_locator'); - } - /** * Compiles arguments/properties for the Phing process. * @return array diff --git a/Command/FixturesLoadCommand.php b/Command/FixturesLoadCommand.php index fed0b29..f1cb9f5 100644 --- a/Command/FixturesLoadCommand.php +++ b/Command/FixturesLoadCommand.php @@ -34,21 +34,25 @@ class FixturesLoadCommand extends AbstractPropelCommand * @var string */ private $defaultFixturesDir = 'app/propel/fixtures'; + /** * Absolute path for fixtures directory * @var string */ private $absoluteFixturesPath = ''; + /** * Filesystem for manipulating files * @var \Symfony\Component\Filesystem\Filesystem */ private $filesystem = null; + /** * Bundle the fixtures are being loaded from * @var Symfony\Component\HttpKernel\Bundle\BundleInterface */ private $bundle; + /** * @see Command */ @@ -108,9 +112,9 @@ YAML fixtures are: Description: Hello world ! EOT - ) + ) ->setName('propel:fixtures:load') - ; + ; } /** @@ -123,19 +127,20 @@ EOT $this->writeSection($output, '[Propel] You are running the command: propel:fixtures:load'); $this->filesystem = new Filesystem(); - + if ('@' === substr($input->getArgument('bundle'), 0, 1)) { $this->bundle = $this ->getContainer() ->get('kernel') ->getBundle(substr($input->getArgument('bundle'), 1)); + $this->absoluteFixturesPath = $this->getFixturesPath($this->bundle); } else { $this->absoluteFixturesPath = realpath($this->getApplication()->getKernel()->getRootDir() . '/../' . $input->getOption('dir')); } if ($input->getOption('verbose')) { - $this->additionalPhingArgs[] = 'verbose'; + $this->additionalPhingArgs[] = 'verbose'; } if (!$this->absoluteFixturesPath && !file_exists($this->absoluteFixturesPath)) { @@ -318,7 +323,7 @@ EOT if (null === $this->bundle) { return $files; } - + $finalFixtureFiles = array(); foreach ($files as $file) { @@ -338,5 +343,5 @@ EOT protected function getFixturesPath(BundleInterface $bundle) { return $bundle->getPath().DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'fixtures'; - } + } } diff --git a/Command/FormGenerateCommand.php b/Command/FormGenerateCommand.php index 662f5a7..2f09cc3 100644 --- a/Command/FormGenerateCommand.php +++ b/Command/FormGenerateCommand.php @@ -1,5 +1,6 @@ */ -class FormGenerateCommand extends ContainerAwareCommand +class FormGenerateCommand extends PropelGeneratorAwareCommand { + const DEFAULT_FORM_TYPE_DIRECTORY = '/Form/Type'; + /** * @see Command */ @@ -29,7 +35,15 @@ class FormGenerateCommand extends ContainerAwareCommand $this ->setDescription('Generate Form types stubs based on the schema.xml') ->addArgument('bundle', InputArgument::REQUIRED, 'The bundle to use to generate Form types') - ->setHelp('') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Overwrite existing Form types') + ->setHelp(<<%command.name% command allows you to quickly generate Form Type stubs for a given bundle. + + php app/console %command.full_name% + +The --force parameter allows you to overwrite existing files. +EOT + ) ->setName('propel:form:generate'); } @@ -40,38 +54,77 @@ class FormGenerateCommand extends ContainerAwareCommand */ protected function execute(InputInterface $input, OutputInterface $output) { - $propelPath = $this->getContainer()->getParameter('propel.path'); - require_once sprintf('%s/generator/lib/builder/util/XmlToAppData.php', $propelPath); - require_once sprintf('%s/generator/lib/config/GeneratorConfig.php', $propelPath); - require_once sprintf('%s/generator/lib/config/QuickGeneratorConfig.php', $propelPath); - - set_include_path(sprintf('%s/generator/lib', $propelPath) . PATH_SEPARATOR . get_include_path()); - if ('@' === substr($input->getArgument('bundle'), 0, 1)) { $bundle = $this ->getContainer() ->get('kernel') ->getBundle(substr($input->getArgument('bundle'), 1)); - if (is_dir($dir = $bundle->getPath().'/Resources/config')) { - $finder = new Finder(); - $schemas = $finder->files()->name('*schema.xml')->followLinks()->in($dir); + $schemas = $this->getSchemasFromBundle($bundle); - $array = array(); - foreach ($schemas as $schema) { - $array[] = $schema->getPathName(); + if ($schemas) { + foreach ($schemas as $fileName => $array) { + foreach ($this->getDatabasesFromSchema($array[1]) as $database) { + $this->createFormTypeFromDatabase($bundle, $database, $output, $input->getOption('force')); + } } + } else { + $output->writeln(sprintf('No *schemas.xml files found in bundle %s.', $bundle->getName())); } - - $transformer = new \XmlToAppData(null, null, 'UTF-8'); - $transformer->setGeneratorConfig(new \QuickGeneratorConfig()); - - $appDatas = array(); - foreach ($array as $xmlFile) { - $appDatas[] = $transformer->parseFile($xmlFile); - } - - var_dump($appDatas); } } + + private function createFormTypeFromDatabase(BundleInterface $bundle, \Database $database, OutputInterface $output, $force = false) + { + $dir = $this->createDirectory($bundle, $output); + + foreach ($database->getTables() as $table) { + $file = new \SplFileInfo(sprintf('%s/%sType.php', $dir, $table->getPhpName())); + + if (!file_exists($file) || true === $force) { + $this->writeFormType($bundle, $table, $file, $force, $output); + } else { + $output->writeln(sprintf('File %-60s exists, skipped. Try the --force option.', $this->getRelativeFileName($file))); + } + } + } + + private function createDirectory(BundleInterface $bundle, OutputInterface $output) + { + $fs = new Filesystem(); + + if (!is_dir($dir = $bundle->getPath() . self::DEFAULT_FORM_TYPE_DIRECTORY)) { + $fs->mkdir($dir); + $this->writeNewDirectory($output, $dir); + } + + return $dir; + } + + private function writeFormType(BundleInterface $bundle, \Table $table, \SplFileInfo $file, $force, OutputInterface $output) + { + $modelName = $table->getPhpName(); + $formTypeContent = file_get_contents(__DIR__ . '/../Resources/skeleton/FormType.php'); + + $formTypeContent = str_replace('##NAMESPACE##', $bundle->getNamespace() . str_replace('/', '\\', self::DEFAULT_FORM_TYPE_DIRECTORY), $formTypeContent); + $formTypeContent = str_replace('##CLASS##', $modelName . 'Type', $formTypeContent); + $formTypeContent = str_replace('##FQCN##', sprintf('%s\%s', $table->getNamespace(), $modelName), $formTypeContent); + $formTypeContent = str_replace('##TYPE_NAME##', strtolower($modelName), $formTypeContent); + $formTypeContent = $this->addFields($table, $formTypeContent); + + file_put_contents($file->getPathName(), $formTypeContent); + $this->writeNewFile($output, $this->getRelativeFileName($file) . ($force ? ' (forced)' : '')); + } + + private function addFields(\Table $table, $formTypeContent) + { + $buildCode = ''; + foreach ($table->getColumns() as $column) { + if (!$column->isPrimaryKey()) { + $buildCode .= sprintf("\n \$builder->add('%s');", lcfirst($column->getPhpName())); + } + } + + return str_replace('##BUILD_CODE##', $buildCode, $formTypeContent); + } } diff --git a/Command/PropelGeneratorAwareCommand.php b/Command/PropelGeneratorAwareCommand.php new file mode 100644 index 0000000..d16cad6 --- /dev/null +++ b/Command/PropelGeneratorAwareCommand.php @@ -0,0 +1,49 @@ + + */ +abstract class PropelGeneratorAwareCommand extends AbstractPropelCommand +{ + /** + * {@inheritdoc} + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + parent::initialize($input, $output); + + $this->loadPropelGenerator(); + } + + protected function loadPropelGenerator() + { + $propelPath = $this->getContainer()->getParameter('propel.path'); + + require_once sprintf('%s/generator/lib/builder/util/XmlToAppData.php', $propelPath); + require_once sprintf('%s/generator/lib/config/GeneratorConfig.php', $propelPath); + require_once sprintf('%s/generator/lib/config/QuickGeneratorConfig.php', $propelPath); + + set_include_path(sprintf('%s/generator/lib', $propelPath) . PATH_SEPARATOR . get_include_path()); + } + + protected function getDatabasesFromSchema(\SplFileInfo $file) + { + $transformer = new \XmlToAppData(null, null, 'UTF-8'); + $transformer->setGeneratorConfig(new \QuickGeneratorConfig()); + + return $transformer->parseFile($file->getPathName())->getDatabases(); + } +} diff --git a/Resources/doc/README.markdown b/Resources/doc/README.markdown index 1e28671..81f125c 100644 --- a/Resources/doc/README.markdown +++ b/Resources/doc/README.markdown @@ -315,6 +315,15 @@ The table arguments define which table will be delete, by default all table. Note that the `--force` option is needed to actually execute the deletion. +### Form Types ### + +You can generate stub classes based on your `schema.xml` in a given bundle: + + > php app/console propel:form:generate [-f|--force] bundle + +It will write Form Type classes in `src/YourVendor/YourBundle/Form/Type`. + + ## PropelParamConverter ## You can use the Propel ParamConverter with the SensioFrameworkExtraBundle. diff --git a/Resources/skeleton/FormType.php b/Resources/skeleton/FormType.php new file mode 100644 index 0000000..9c96e81 --- /dev/null +++ b/Resources/skeleton/FormType.php @@ -0,0 +1,34 @@ + '##FQCN##', + ); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return '##TYPE_NAME##'; + } +} diff --git a/Tests/Command/AbstractPropelCommandTest.php b/Tests/Command/AbstractPropelCommandTest.php index 5a17837..ca02639 100644 --- a/Tests/Command/AbstractPropelCommandTest.php +++ b/Tests/Command/AbstractPropelCommandTest.php @@ -8,7 +8,7 @@ * @license MIT License */ -namespace Tests\Command; +namespace Propel\PropelBundle\Tests\Command; use Propel\PropelBundle\Tests\TestCase; use Propel\PropelBundle\Command\AbstractPropelCommand; diff --git a/Tests/FixturesLoadCommandTest.php b/Tests/Command/FixturesLoadCommandTest.php similarity index 97% rename from Tests/FixturesLoadCommandTest.php rename to Tests/Command/FixturesLoadCommandTest.php index 2a85414..29aeb46 100644 --- a/Tests/FixturesLoadCommandTest.php +++ b/Tests/Command/FixturesLoadCommandTest.php @@ -8,7 +8,7 @@ * @license MIT License */ -namespace Tests\Command; +namespace Propel\PropelBundle\Tests\Command; use Symfony\Component\Filesystem\Filesystem; diff --git a/Tests/Command/PropelGeneratorAwareCommandTest.php b/Tests/Command/PropelGeneratorAwareCommandTest.php new file mode 100644 index 0000000..93cbc4d --- /dev/null +++ b/Tests/Command/PropelGeneratorAwareCommandTest.php @@ -0,0 +1,73 @@ + + */ +class PropelGeneratorAwareCommandTest extends TestCase +{ + protected $container; + + protected function setUp() + { + parent::setUp(); + + $this->container = $this->getContainer(); + $this->container->setParameter('propel.path', __DIR__ . '/../../vendor/propel'); + } + + public function testGetDatabasesFromSchema() + { + $command = new PropelGeneratorAwareCommandTestable('testable-command'); + $command->setContainer($this->container); + $databases = $command->getDatabasesFromSchema(new \SplFileInfo(__DIR__ . '/../Fixtures/schema.xml')); + + $this->assertTrue(is_array($databases)); + + foreach ($databases as $database) { + $this->assertInstanceOf('\Database', $database); + } + + $bookstore = $databases[0]; + $this->assertEquals(1, count($bookstore->getTables())); + + foreach ($bookstore->getTables() as $table) { + $this->assertInstanceOf('\Table', $table); + } + } +} + +class PropelGeneratorAwareCommandTestable extends PropelGeneratorAwareCommand +{ + protected $container; + + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + + protected function getContainer() + { + return $this->container; + } + + public function getDatabasesFromSchema(\SplFileInfo $file) + { + $this->loadPropelGenerator(); + + return parent::getDatabasesFromSchema($file); + } +} diff --git a/Tests/DependencyInjection/PropelExtensionTest.php b/Tests/DependencyInjection/PropelExtensionTest.php index 63379cd..9a8444b 100644 --- a/Tests/DependencyInjection/PropelExtensionTest.php +++ b/Tests/DependencyInjection/PropelExtensionTest.php @@ -44,9 +44,9 @@ class PropelExtensionTest extends TestCase $container = $this->getContainer(); $loader = new PropelExtension(); $loader->load(array(array( - 'path' => '/propel', + 'path' => '/propel', 'phing_path' => '/phing', - 'dbal' => array() + 'dbal' => array() )), $container); $this->assertEquals('/propel', $container->getParameter('propel.path'), '->load() requires the Propel path'); $this->assertEquals('/phing', $container->getParameter('propel.phing_path'), '->load() requires the Phing path'); @@ -270,7 +270,7 @@ class PropelExtensionTest extends TestCase $this->assertArrayHasKey('query', $config['datasources']['default']['connection']['settings']['queries']); $this->assertEquals('SET NAMES UTF8', $config['datasources']['default']['connection']['settings']['queries']['query']); } - + public function testDbalWithSlaves() { $container = $this->getContainer(); @@ -299,13 +299,13 @@ class PropelExtensionTest extends TestCase ), ), ); - + $configs = array($config_base, array('dbal' => $config_mysql)); $loader->load($configs, $container); $arguments = $container->getDefinition('propel.configuration')->getArguments(); $config = $arguments[0]; - + $this->assertArrayHasKey('slaves', $config['datasources']['default']); $this->assertArrayHasKey('connection', $config['datasources']['default']['slaves']); $this->assertArrayHasKey('mysql_slave1', $config['datasources']['default']['slaves']['connection']); diff --git a/Tests/Fixtures/schema.xml b/Tests/Fixtures/schema.xml new file mode 100644 index 0000000..3e7f0de --- /dev/null +++ b/Tests/Fixtures/schema.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + +
+