From d9bbe8e359e3405485ac4df5589c7b2b909f2503 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 13 Sep 2024 22:59:21 -0400 Subject: [PATCH 01/11] Update version for 5.1.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f7a4a2d0..20cbc0d3 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "require": { "php": ">=8.1", "brick/varexporter": "^0.5.0", - "cakephp/cakephp": "dev-5.next as 5.1.0", + "cakephp/cakephp": "^5.1", "cakephp/twig-view": "^2.0.0", "nikic/php-parser": "^5.0.0" }, From 02a449b3f2bc4adc955b32d0b73307f7e6b3f607 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 4 Oct 2024 13:27:34 +0200 Subject: [PATCH 02/11] add note to remove method hooks in generated template if not needed --- templates/bake/Plugin/src/Plugin.php.twig | 5 +++++ .../comparisons/Plugin/Company/Example/src/ExamplePlugin.php | 5 +++++ .../Plugin/SimpleExample/src/SimpleExamplePlugin.php | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/templates/bake/Plugin/src/Plugin.php.twig b/templates/bake/Plugin/src/Plugin.php.twig index a7d1575a..2892c840 100644 --- a/templates/bake/Plugin/src/Plugin.php.twig +++ b/templates/bake/Plugin/src/Plugin.php.twig @@ -41,6 +41,7 @@ class {{ name }}Plugin extends BasePlugin */ public function bootstrap(PluginApplicationInterface $app): void { + // remove this method hook if you don't need it } /** @@ -54,6 +55,7 @@ class {{ name }}Plugin extends BasePlugin */ public function routes(RouteBuilder $routes): void { + // remove this method hook if you don't need it $routes->plugin( '{{ plugin }}', ['path' => '/{{ routePath }}'], @@ -75,6 +77,7 @@ class {{ name }}Plugin extends BasePlugin public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue { // Add your middlewares here + // remove this method hook if you don't need it return $middlewareQueue; } @@ -88,6 +91,7 @@ class {{ name }}Plugin extends BasePlugin public function console(CommandCollection $commands): CommandCollection { // Add your commands here + // remove this method hook if you don't need it $commands = parent::console($commands); @@ -104,5 +108,6 @@ class {{ name }}Plugin extends BasePlugin public function services(ContainerInterface $container): void { // Add your services here + // remove this method hook if you don't need it } } diff --git a/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php b/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php index 1d947085..ac42bb1a 100644 --- a/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php +++ b/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php @@ -26,6 +26,7 @@ class ExamplePlugin extends BasePlugin */ public function bootstrap(PluginApplicationInterface $app): void { + // remove this method hook if you don't need it } /** @@ -39,6 +40,7 @@ public function bootstrap(PluginApplicationInterface $app): void */ public function routes(RouteBuilder $routes): void { + // remove this method hook if you don't need it $routes->plugin( 'Company/Example', ['path' => '/company/example'], @@ -60,6 +62,7 @@ function (RouteBuilder $builder) { public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue { // Add your middlewares here + // remove this method hook if you don't need it return $middlewareQueue; } @@ -73,6 +76,7 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue public function console(CommandCollection $commands): CommandCollection { // Add your commands here + // remove this method hook if you don't need it $commands = parent::console($commands); @@ -89,5 +93,6 @@ public function console(CommandCollection $commands): CommandCollection public function services(ContainerInterface $container): void { // Add your services here + // remove this method hook if you don't need it } } diff --git a/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php b/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php index 45bbf490..525f3b8a 100644 --- a/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php +++ b/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php @@ -26,6 +26,7 @@ class SimpleExamplePlugin extends BasePlugin */ public function bootstrap(PluginApplicationInterface $app): void { + // remove this method hook if you don't need it } /** @@ -39,6 +40,7 @@ public function bootstrap(PluginApplicationInterface $app): void */ public function routes(RouteBuilder $routes): void { + // remove this method hook if you don't need it $routes->plugin( 'SimpleExample', ['path' => '/simple-example'], @@ -60,6 +62,7 @@ function (RouteBuilder $builder) { public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue { // Add your middlewares here + // remove this method hook if you don't need it return $middlewareQueue; } @@ -73,6 +76,7 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue public function console(CommandCollection $commands): CommandCollection { // Add your commands here + // remove this method hook if you don't need it $commands = parent::console($commands); @@ -89,5 +93,6 @@ public function console(CommandCollection $commands): CommandCollection public function services(ContainerInterface $container): void { // Add your services here + // remove this method hook if you don't need it } } From 04b86ae43b4bce2a907fd05790edee42922f871c Mon Sep 17 00:00:00 2001 From: ADmad Date: Sun, 6 Oct 2024 23:02:33 +0530 Subject: [PATCH 03/11] Add `standalone-path` option for plugin commmand. This allows generating standalone plugins in paths outside the app. By default in-app generated plugins no longer have unneeded files like composer.json etc. Closes #998 --- src/Command/PluginCommand.php | 71 ++++++++++++++----- tests/TestCase/Command/PluginCommandTest.php | 25 +++++-- .../Plugin/SimpleExample/.gitignore | 8 --- .../Plugin/SimpleExample/README.md | 11 --- .../Plugin/SimpleExample/composer.json | 24 ------- .../Plugin/SimpleExample/phpunit.xml.dist | 30 -------- .../Plugin/SimpleExample/tests/bootstrap.php | 55 -------------- .../Plugin/SimpleExample/tests/schema.sql | 1 - 8 files changed, 73 insertions(+), 152 deletions(-) delete mode 100644 tests/comparisons/Plugin/SimpleExample/.gitignore delete mode 100644 tests/comparisons/Plugin/SimpleExample/README.md delete mode 100644 tests/comparisons/Plugin/SimpleExample/composer.json delete mode 100644 tests/comparisons/Plugin/SimpleExample/phpunit.xml.dist delete mode 100644 tests/comparisons/Plugin/SimpleExample/tests/bootstrap.php delete mode 100644 tests/comparisons/Plugin/SimpleExample/tests/schema.sql diff --git a/src/Command/PluginCommand.php b/src/Command/PluginCommand.php index 6ca6c694..053006aa 100644 --- a/src/Command/PluginCommand.php +++ b/src/Command/PluginCommand.php @@ -43,6 +43,8 @@ class PluginCommand extends BakeCommand */ public string $path; + protected bool $isVendor = false; + /** * initialize * @@ -73,6 +75,18 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $parts = explode('/', $name); $plugin = implode('/', array_map([Inflector::class, 'camelize'], $parts)); + if ($args->getOption('standalone-path')) { + $this->path = $args->getOption('standalone-path'); + $this->path = rtrim($this->path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $this->isVendor = true; + + if (!is_dir($this->path)) { + $io->err(sprintf('Path `%s` does not exist.', $this->path)); + + return static::CODE_ERROR; + } + } + $pluginPath = $this->_pluginPath($plugin); if (is_dir($pluginPath)) { $io->out(sprintf('Plugin: %s already exists, no action taken', $plugin)); @@ -115,24 +129,27 @@ public function bake(string $plugin, Arguments $args, ConsoleIo $io): ?bool } $this->_generateFiles($plugin, $this->path, $args, $io); - $this->_modifyApplication($plugin, $io); - $composer = $this->findComposer($args, $io); + if (!$this->isVendor) { + $this->_modifyApplication($plugin, $io); - try { - $cwd = getcwd(); + $composer = $this->findComposer($args, $io); - // Windows makes running multiple commands at once hard. - chdir(dirname($this->_rootComposerFilePath())); - $command = 'php ' . escapeshellarg($composer) . ' dump-autoload'; - $process = new Process($io); - $io->out($process->call($command)); + try { + $cwd = getcwd(); - chdir($cwd); - } catch (RuntimeException $e) { - $error = $e->getMessage(); - $io->error(sprintf('Could not run `composer dump-autoload`: %s', $error)); - $this->abort(); + // Windows makes running multiple commands at once hard. + chdir(dirname($this->_rootComposerFilePath())); + $command = 'php ' . escapeshellarg($composer) . ' dump-autoload'; + $process = new Process($io); + $io->out($process->call($command)); + + chdir($cwd); + } catch (RuntimeException $e) { + $error = $e->getMessage(); + $io->error(sprintf('Could not run `composer dump-autoload`: %s', $error)); + $this->abort(); + } } $io->hr(); @@ -219,9 +236,24 @@ protected function _generateFiles( do { $templatesPath = array_shift($paths) . BakeView::BAKE_TEMPLATE_FOLDER . '/Plugin'; if (is_dir($templatesPath)) { - $templates = array_keys(iterator_to_array( + $files = iterator_to_array( $fs->findRecursive($templatesPath, '/\.twig$/') - )); + ); + + if (!$this->isVendor) { + $vendorFiles = [ + '.gitignore.twig', 'README.md.twig', 'composer.json.twig', 'phpunit.xml.dist.twig', + 'bootstrap.php.twig', 'schema.sql.twig', + ]; + + foreach ($files as $key => $file) { + if (in_array($file->getFilename(), $vendorFiles, true)) { + unset($files[$key]); + } + } + } + + $templates = array_keys($files); } } while (!$templates); @@ -326,7 +358,8 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'Create the directory structure, AppController class and testing setup for a new plugin. ' . 'Can create plugins in any of your bootstrapped plugin paths.' )->addArgument('name', [ - 'help' => 'CamelCased name of the plugin to create.', + 'help' => 'CamelCased name of the plugin to create.' + . ' For standalone plugins you can use vendor prefixed names like MyVendor/MyPlugin.', ])->addOption('composer', [ 'default' => ROOT . DS . 'composer.phar', 'help' => 'The path to the composer executable.', @@ -339,6 +372,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => 'The theme to use when baking code.', 'default' => Configure::read('Bake.theme') ?: null, 'choices' => $this->_getBakeThemes(), + ]) + ->addOption('standalone-path', [ + 'short' => 'p', + 'help' => 'Generate a standalone plugin in the provided path.', ]); return $parser; diff --git a/tests/TestCase/Command/PluginCommandTest.php b/tests/TestCase/Command/PluginCommandTest.php index d657730e..4936f638 100644 --- a/tests/TestCase/Command/PluginCommandTest.php +++ b/tests/TestCase/Command/PluginCommandTest.php @@ -36,6 +36,8 @@ class PluginCommandTest extends TestCase protected $pluginsPath = TMP . 'plugin_task' . DS; + protected string $pluginsStandalonePath = TMP . 'plugin_standalone_task' . DS; + /** * setUp method * @@ -55,6 +57,11 @@ public function setUp(): void mkdir($this->pluginsPath, 0777, true); } + // Create the test output path + if (!file_exists($this->pluginsStandalonePath)) { + mkdir($this->pluginsStandalonePath, 0777, true); + } + if (file_exists(APP . 'Application.php.bak')) { rename(APP . 'Application.php.bak', APP . 'Application.php'); } else { @@ -71,6 +78,7 @@ public function tearDown(): void { $fs = new Filesystem(); $fs->deleteDir($this->pluginsPath); + $fs->deleteDir($this->pluginsStandalonePath); if (file_exists(APP . 'Application.php.bak')) { rename(APP . 'Application.php.bak', APP . 'Application.php'); @@ -123,9 +131,9 @@ public function testMainCustomAppNamespace() */ public function testMainVendorName() { - $this->exec('bake plugin Company/Example', ['y', 'n']); + $this->exec('bake plugin Company/Example --standalone-path ' . $this->pluginsStandalonePath, ['y', 'n']); $this->assertExitCode(CommandInterface::CODE_SUCCESS); - $this->assertPluginContents('Company/Example'); + $this->assertPluginContents('Company/Example', true); } /** @@ -135,9 +143,9 @@ public function testMainVendorName() */ public function testMainVendorNameCasingFix() { - $this->exec('bake plugin company/example', ['y', 'n']); + $this->exec('bake plugin company/example --standalone-path ' . $this->pluginsStandalonePath, ['y', 'n']); $this->assertExitCode(CommandInterface::CODE_SUCCESS); - $this->assertPluginContents('Company/Example'); + $this->assertPluginContents('Company/Example', true); } /** @@ -231,15 +239,20 @@ public function testFindPathEmpty() * Compare to a static copy of the plugin in the comparison folder * * @param string $pluginName the name of the plugin to compare to + * @param bool $vendor Whether testing a vendor plugin generation * @return void */ - public function assertPluginContents($pluginName) + public function assertPluginContents($pluginName, bool $vendor = false): void { $pluginName = str_replace('/', DS, $pluginName); $comparisonRoot = $this->_compareBasePath . $pluginName . DS; $comparisonFiles = $this->getFiles($comparisonRoot); - $bakedRoot = App::path('plugins')[0] . $pluginName . DS; + if ($vendor) { + $bakedRoot = $this->pluginsStandalonePath . $pluginName . DS; + } else { + $bakedRoot = App::path('plugins')[0] . $pluginName . DS; + } $bakedFiles = $this->getFiles($bakedRoot); $this->assertCount( diff --git a/tests/comparisons/Plugin/SimpleExample/.gitignore b/tests/comparisons/Plugin/SimpleExample/.gitignore deleted file mode 100644 index 244d127b..00000000 --- a/tests/comparisons/Plugin/SimpleExample/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -/composer.lock -/composer.phar -/phpunit.xml -/.phpunit.result.cache -/phpunit.phar -/config/Migrations/schema-dump-default.lock -/vendor/ -/.idea/ diff --git a/tests/comparisons/Plugin/SimpleExample/README.md b/tests/comparisons/Plugin/SimpleExample/README.md deleted file mode 100644 index 889409c7..00000000 --- a/tests/comparisons/Plugin/SimpleExample/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# SimpleExample plugin for CakePHP - -## Installation - -You can install this plugin into your CakePHP application using [composer](https://getcomposer.org). - -The recommended way to install composer packages is: - -``` -composer require your-name-here/simple-example -``` diff --git a/tests/comparisons/Plugin/SimpleExample/composer.json b/tests/comparisons/Plugin/SimpleExample/composer.json deleted file mode 100644 index 9a29c635..00000000 --- a/tests/comparisons/Plugin/SimpleExample/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "your-name-here/simple-example", - "description": "SimpleExample plugin for CakePHP", - "type": "cakephp-plugin", - "license": "MIT", - "require": { - "php": ">=8.1", - "cakephp/cakephp": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "autoload": { - "psr-4": { - "SimpleExample\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "SimpleExample\\Test\\": "tests/", - "Cake\\Test\\": "vendor/cakephp/cakephp/tests/" - } - } -} diff --git a/tests/comparisons/Plugin/SimpleExample/phpunit.xml.dist b/tests/comparisons/Plugin/SimpleExample/phpunit.xml.dist deleted file mode 100644 index 3d467294..00000000 --- a/tests/comparisons/Plugin/SimpleExample/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - tests/TestCase/ - - - - - - - - - - - src/ - - - diff --git a/tests/comparisons/Plugin/SimpleExample/tests/bootstrap.php b/tests/comparisons/Plugin/SimpleExample/tests/bootstrap.php deleted file mode 100644 index 445c88c0..00000000 --- a/tests/comparisons/Plugin/SimpleExample/tests/bootstrap.php +++ /dev/null @@ -1,55 +0,0 @@ -loadSqlFiles('tests/schema.sql', 'test'); diff --git a/tests/comparisons/Plugin/SimpleExample/tests/schema.sql b/tests/comparisons/Plugin/SimpleExample/tests/schema.sql deleted file mode 100644 index e569a960..00000000 --- a/tests/comparisons/Plugin/SimpleExample/tests/schema.sql +++ /dev/null @@ -1 +0,0 @@ --- Test database schema for SimpleExample From be167ce7af888e2d317e18ad6ddb1f0dc8b12a21 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 8 Oct 2024 12:06:18 +0530 Subject: [PATCH 04/11] Update CakePHP version in URLs --- docs/en/development.rst | 4 ++-- docs/fr/development.rst | 4 ++-- templates/bake/Command/command.twig | 2 +- templates/bake/Plugin/src/Plugin.php.twig | 2 +- tests/comparisons/Command/testBakePlugin.php | 2 +- .../comparisons/Plugin/Company/Example/src/ExamplePlugin.php | 2 +- .../Plugin/SimpleExample/src/SimpleExamplePlugin.php | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/en/development.rst b/docs/en/development.rst index dd66b99f..c7c7aa25 100644 --- a/docs/en/development.rst +++ b/docs/en/development.rst @@ -129,7 +129,7 @@ looks like this:: /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ @@ -172,7 +172,7 @@ And the resultant baked class (**src/Command/FooCommand.php**) looks like this:: /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ diff --git a/docs/fr/development.rst b/docs/fr/development.rst index f9a24c12..697a6301 100644 --- a/docs/fr/development.rst +++ b/docs/fr/development.rst @@ -137,7 +137,7 @@ ressemble à ceci:: /** * Méthode hook pour définir le parseur d'option de cette commande. * - * @see https://book.cakephp.org/4/fr/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/fr/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser Le parseur à définir * @return \Cake\Console\ConsoleOptionParser Le parseur construit. */ @@ -181,7 +181,7 @@ ressemble à ceci:: /** * Méthode hook pour définir le parseur d'option de cette commande. * - * @see https://book.cakephp.org/4/fr/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/fr/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser Le parseur à définir * @return \Cake\Console\ConsoleOptionParser Le parseur construit. */ diff --git a/templates/bake/Command/command.twig b/templates/bake/Command/command.twig index fe126f6c..d649080d 100644 --- a/templates/bake/Command/command.twig +++ b/templates/bake/Command/command.twig @@ -31,7 +31,7 @@ class {{ name }}Command extends Command /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ diff --git a/templates/bake/Plugin/src/Plugin.php.twig b/templates/bake/Plugin/src/Plugin.php.twig index 2892c840..e1982d0f 100644 --- a/templates/bake/Plugin/src/Plugin.php.twig +++ b/templates/bake/Plugin/src/Plugin.php.twig @@ -103,7 +103,7 @@ class {{ name }}Plugin extends BasePlugin * * @param \Cake\Core\ContainerInterface $container The Container to update. * @return void - * @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection + * @link https://book.cakephp.org/5/en/development/dependency-injection.html#dependency-injection */ public function services(ContainerInterface $container): void { diff --git a/tests/comparisons/Command/testBakePlugin.php b/tests/comparisons/Command/testBakePlugin.php index c762139d..94f17b9c 100644 --- a/tests/comparisons/Command/testBakePlugin.php +++ b/tests/comparisons/Command/testBakePlugin.php @@ -16,7 +16,7 @@ class ExampleCommand extends Command /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ diff --git a/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php b/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php index ac42bb1a..197dbdf8 100644 --- a/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php +++ b/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php @@ -88,7 +88,7 @@ public function console(CommandCollection $commands): CommandCollection * * @param \Cake\Core\ContainerInterface $container The Container to update. * @return void - * @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection + * @link https://book.cakephp.org/5/en/development/dependency-injection.html#dependency-injection */ public function services(ContainerInterface $container): void { diff --git a/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php b/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php index 525f3b8a..d53ed8b1 100644 --- a/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php +++ b/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php @@ -88,7 +88,7 @@ public function console(CommandCollection $commands): CommandCollection * * @param \Cake\Core\ContainerInterface $container The Container to update. * @return void - * @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection + * @link https://book.cakephp.org/5/en/development/dependency-injection.html#dependency-injection */ public function services(ContainerInterface $container): void { From 8be277cd215333f201de95a426f4c86ad32c1c54 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 8 Oct 2024 12:04:52 +0530 Subject: [PATCH 05/11] Add getDescription() to command template --- templates/bake/Command/command.twig | 17 +++++++++++++---- tests/comparisons/Command/testBakePlugin.php | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/templates/bake/Command/command.twig b/templates/bake/Command/command.twig index fe126f6c..3654c334 100644 --- a/templates/bake/Command/command.twig +++ b/templates/bake/Command/command.twig @@ -28,18 +28,27 @@ */ class {{ name }}Command extends Command { + /** + * Get the command description. + * + * @return string + */ + public static function getDescription(): string + { + return 'Command description here.'; + } + /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser = parent::buildOptionParser($parser); - - return $parser; + return parent::buildOptionParser($parser) + ->setDescription(static::getDescription()); } /** diff --git a/tests/comparisons/Command/testBakePlugin.php b/tests/comparisons/Command/testBakePlugin.php index c762139d..da518ce2 100644 --- a/tests/comparisons/Command/testBakePlugin.php +++ b/tests/comparisons/Command/testBakePlugin.php @@ -13,18 +13,27 @@ */ class ExampleCommand extends Command { + /** + * Get the command description. + * + * @return string + */ + public static function getDescription(): string + { + return 'Command description here.'; + } + /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser = parent::buildOptionParser($parser); - - return $parser; + return parent::buildOptionParser($parser) + ->setDescription(static::getDescription()); } /** From 5b61eb4dd098e77ee78b0d2b1a04677628c7e5a3 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Sun, 17 Nov 2024 15:50:26 +0100 Subject: [PATCH 06/11] update stan --- .phive/phars.xml | 4 ++-- phpstan-baseline.neon | 12 ++++++++++-- src/BakePlugin.php | 7 ++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 726b7777..d311bfa5 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,5 +1,5 @@ - - + + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 091e0a30..e45840cf 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,11 +1,19 @@ parameters: ignoreErrors: - - message: "#^Instanceof between mixed and Cake\\\\Chronos\\\\Chronos will always evaluate to false\\.$#" + message: '#^Method Bake\\BakePlugin\:\:bootstrap\(\) has parameter \$app with generic interface Cake\\Core\\PluginApplicationInterface but does not specify its types\: TSubject$#' + identifier: missingType.generics + count: 1 + path: src/BakePlugin.php + + - + message: '#^Instanceof between mixed and Cake\\Chronos\\Chronos will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse count: 1 path: src/Command/FixtureCommand.php - - message: "#^Dead catch \\- UnexpectedValueException is never thrown in the try block\\.$#" + message: '#^Dead catch \- UnexpectedValueException is never thrown in the try block\.$#' + identifier: catch.neverThrown count: 1 path: src/Command/TestCommand.php diff --git a/src/BakePlugin.php b/src/BakePlugin.php index 9e491904..bb91af0d 100644 --- a/src/BakePlugin.php +++ b/src/BakePlugin.php @@ -50,7 +50,6 @@ class BakePlugin extends BasePlugin /** * Load the TwigView plugin. * - * @phpstan-ignore-next-line * @param \Cake\Core\PluginApplicationInterface $app The host application * @return void */ @@ -138,22 +137,20 @@ protected function findInPath(string $namespace, string $path): array if ($item->isDot() || $item->isDir()) { continue; } - /** @psalm-var class-string<\Bake\Command\BakeCommand> $class */ $class = $namespace . $item->getBasename('.php'); if (!$hasSubfolder) { try { $reflection = new ReflectionClass($class); - /** @phpstan-ignore-next-line */ - } catch (ReflectionException $e) { + } catch (ReflectionException) { continue; } - /** @psalm-suppress TypeDoesNotContainType */ if (!$reflection->isInstantiable() || !$reflection->isSubclassOf(BakeCommand::class)) { continue; } } + /** @var class-string<\Bake\Command\BakeCommand> $class */ $candidates[$class::defaultName()] = $class; } From 0fc3664c9b95f63d5195009ddd554066f18c0c05 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Thu, 21 Nov 2024 01:07:46 +0100 Subject: [PATCH 07/11] 3.x-bake-model-relations --- docs/en/usage.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/en/usage.rst b/docs/en/usage.rst index 29da6dcb..8cf9da92 100644 --- a/docs/en/usage.rst +++ b/docs/en/usage.rst @@ -52,6 +52,18 @@ You can get the list of available bake command by running ``bin/cake bake --help To run a command, type `cake command_name [args|options]` To get help on a specific command, type `cake command_name --help` +Bake Models +=========== + +Models are generically baked from the existing DB tables. +The conventions here apply, so it will detect relations based on ``thing_id`` foreign keys to ``things`` tables with their ``id`` primary keys. + +For non-conventional relations, you can use references in the constraints / foreign key definitions for Bake to detect the relations, e.g.:: + + ->addForeignKey('billing_country_id', 'countries') // defaults to `id` + ->addForeignKey('shipping_country_id', 'countries', 'cid') + + Bake Themes =========== From 4a4a1ff7bd8b117455ddcf3fa4269eb8dbf58712 Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 27 Nov 2024 13:37:34 +0100 Subject: [PATCH 08/11] Allow all --everything to run through using force mode. --- src/Command/AllCommand.php | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Command/AllCommand.php b/src/Command/AllCommand.php index 34eeeeeb..5d6244bd 100644 --- a/src/Command/AllCommand.php +++ b/src/Command/AllCommand.php @@ -21,6 +21,7 @@ use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionManager; +use Throwable; /** * Command for `bake all` @@ -49,7 +50,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser = $this->_setCommonOptions($parser); $parser = $parser->setDescription( - 'Generate the model, controller, template, tests and fixture for a table.' + 'Generate the model, controller, template, tests and fixture for a table.', )->addArgument('name', [ 'help' => 'Name of the table to generate code for.', ])->addOption('everything', [ @@ -83,7 +84,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); - if (empty($name) && !$args->getOption('everything')) { + if (!$name && !$args->getOption('everything')) { $io->out('Choose a table to generate from the following:'); foreach ($scanner->listUnskipped() as $table) { $io->out('- ' . $this->_camelize($table)); @@ -110,15 +111,31 @@ public function execute(Arguments $args, ConsoleIo $io): ?int unset($options['prefix']); } + $errors = 0; foreach ($tables as $table) { $parser = $command->getOptionParser(); $subArgs = new Arguments([$table], $options, $parser->argumentNames()); - $command->execute($subArgs, $io); + + try { + $command->execute($subArgs, $io); + } catch (Throwable $e) { + if (!$args->getOption('everything') || !$args->getOption('force')) { + throw $e; + } + + $message = sprintf('Error generating %s for %s: %s', $commandName, $table, $e->getMessage()); + $io->err('' . $message . ''); + $errors++; + } } } - $io->out('Bake All complete.', 1, ConsoleIo::NORMAL); + if ($errors) { + $io->out(sprintf('Bake All completed, but with %s errors.', $errors), 1, ConsoleIo::NORMAL); + } else { + $io->out('Bake All complete.', 1, ConsoleIo::NORMAL); + } - return static::CODE_SUCCESS; + return $errors ? static::CODE_ERROR : static::CODE_SUCCESS; } } From cc4d4969219b049fedc4b1b8b8cfddd47611253e Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 27 Nov 2024 14:05:33 +0100 Subject: [PATCH 09/11] Allow all --everything to run through using force mode. --- src/Command/AllCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/AllCommand.php b/src/Command/AllCommand.php index 5d6244bd..6768c05e 100644 --- a/src/Command/AllCommand.php +++ b/src/Command/AllCommand.php @@ -98,6 +98,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $tables = [$name]; } + $errors = 0; foreach ($this->commands as $commandName) { /** @var \Cake\Command\Command $command */ $command = new $commandName(); @@ -111,7 +112,6 @@ public function execute(Arguments $args, ConsoleIo $io): ?int unset($options['prefix']); } - $errors = 0; foreach ($tables as $table) { $parser = $command->getOptionParser(); $subArgs = new Arguments([$table], $options, $parser->argumentNames()); From 0f28cba3d73a2fbcca9ff087ea15310ba2aa1ba5 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Fri, 29 Nov 2024 00:38:40 +0100 Subject: [PATCH 10/11] Make sure custom relations work out with new alias strictness. (#1015) * Make sure custom relations work out with new alias strictness. * Add more tests. --------- Co-authored-by: Luiz Marin <67489841+luizcmarin@users.noreply.github.com> --- src/Command/ModelCommand.php | 49 ++++++++- tests/Fixture/RelationsFixture.php | 36 +++++++ tests/TestCase/Command/ModelCommandTest.php | 41 +++++++- .../Model/testBakeEntityCustomHidden.php | 1 + .../Model/testBakeEntityFullContext.php | 2 + .../Model/testBakeEntityHidden.php | 1 + .../Model/testBakeEntitySimple.php | 1 + .../Model/testBakeEntitySimpleUnchanged.php | 1 + .../Model/testBakeEntityWithPlugin.php | 2 + .../Model/testBakeTableWithPlugin.php | 5 + .../Model/testBakeWithRulesUnique.php | 4 + tests/schema.php | 28 ++++++ .../App/Controller/RelationsController.php | 99 +++++++++++++++++++ 13 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 tests/Fixture/RelationsFixture.php create mode 100644 tests/test_app/App/Controller/RelationsController.php diff --git a/src/Command/ModelCommand.php b/src/Command/ModelCommand.php index 423f088e..60c07959 100644 --- a/src/Command/ModelCommand.php +++ b/src/Command/ModelCommand.php @@ -256,6 +256,8 @@ public function getAssociations(Table $table, Arguments $args, ConsoleIo $io): a $associations = $this->findHasMany($table, $associations); $associations = $this->findBelongsToMany($table, $associations); + $associations = $this->ensureAliasUniqueness($associations); + return $associations; } @@ -275,6 +277,7 @@ public function applyAssociations(Table $model, array $associations): void if (get_class($model) !== Table::class) { return; } + foreach ($associations as $type => $assocs) { foreach ($assocs as $assoc) { $alias = $assoc['alias']; @@ -349,6 +352,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg continue; } + $className = null; if ($fieldName === 'parent_id') { $className = $this->plugin ? $this->plugin . '.' . $model->getAlias() : $model->getAlias(); $assoc = [ @@ -375,7 +379,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg $allowAliasRelations = $args && $args->getOption('skip-relation-check'); $found = $this->findTableReferencedBy($schema, $fieldName); if ($found) { - $tmpModelName = Inflector::camelize($found); + $className = ($this->plugin ? $this->plugin . '.' : '') . Inflector::camelize($found); } elseif (!$allowAliasRelations) { continue; } @@ -384,6 +388,9 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg 'alias' => $tmpModelName, 'foreignKey' => $fieldName, ]; + if ($className && $className !== $tmpModelName) { + $assoc['className'] = $className; + } if ($schema->getColumn($fieldName)['null'] === false) { $assoc['joinType'] = 'INNER'; } @@ -392,6 +399,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg if ($this->plugin && empty($assoc['className'])) { $assoc['className'] = $this->plugin . '.' . $assoc['alias']; } + $associations['belongsTo'][] = $assoc; } @@ -1546,4 +1554,43 @@ protected function bakeEnums(Table $model, array $data, Arguments $args, Console $enumCommand->execute($args, $io); } } + + /** + * @param array> $associations + * @return array> + */ + protected function ensureAliasUniqueness(array $associations): array + { + $existing = []; + foreach ($associations as $type => $associationsPerType) { + foreach ($associationsPerType as $k => $association) { + $alias = $association['alias']; + if (in_array($alias, $existing, true)) { + $alias = $this->createAssociationAlias($association); + } + $existing[] = $alias; + if (empty($association['className'])) { + $className = $this->plugin ? $this->plugin . '.' . $association['alias'] : $association['alias']; + if ($className !== $alias) { + $association['className'] = $className; + } + } + $association['alias'] = $alias; + $associations[$type][$k] = $association; + } + } + + return $associations; + } + + /** + * @param array $association + * @return string + */ + protected function createAssociationAlias(array $association): string + { + $foreignKey = $association['foreignKey']; + + return $this->_modelNameFromKey($foreignKey); + } } diff --git a/tests/Fixture/RelationsFixture.php b/tests/Fixture/RelationsFixture.php new file mode 100644 index 00000000..d7b09a1f --- /dev/null +++ b/tests/Fixture/RelationsFixture.php @@ -0,0 +1,36 @@ + 1, + 'other_id' => 1, + 'body' => 'Try it out!', + ], + ]; +} diff --git a/tests/TestCase/Command/ModelCommandTest.php b/tests/TestCase/Command/ModelCommandTest.php index c63f5496..91cce3a3 100644 --- a/tests/TestCase/Command/ModelCommandTest.php +++ b/tests/TestCase/Command/ModelCommandTest.php @@ -597,14 +597,16 @@ public function testGetAssociationsConstraints() $expected = [ [ - 'alias' => 'Users', + 'alias' => 'Senders', 'foreignKey' => 'sender_id', 'joinType' => 'INNER', + 'className' => 'Users', ], [ - 'alias' => 'Users', + 'alias' => 'Receivers', 'foreignKey' => 'receiver_id', 'joinType' => 'INNER', + 'className' => 'Users', ], ]; $this->assertEquals($expected, $result['belongsTo']); @@ -667,7 +669,7 @@ public function testBelongsToGeneration() */ public function testBelongsToGenerationConstraints() { - $model = $this->getTableLocator()->get('Invitations'); + $model = $this->getTableLocator()->get('Relations'); $command = new ModelCommand(); $command->connection = 'test'; $result = $command->findBelongsTo($model, []); @@ -675,13 +677,44 @@ public function testBelongsToGenerationConstraints() 'belongsTo' => [ [ 'alias' => 'Users', + 'foreignKey' => 'user_id', + 'joinType' => 'INNER', + ], + [ + 'alias' => 'Others', + 'foreignKey' => 'other_id', + 'joinType' => 'INNER', + 'className' => 'Users', + ], + ], + ]; + $this->assertEquals($expected, $result); + } + + /** + * Test that belongsTo association generation uses aliased constraints on the table + * + * @return void + */ + public function testBelongsToGenerationConstraintsAliased() + { + $model = $this->getTableLocator()->get('Invitations'); + $command = new ModelCommand(); + $command->connection = 'test'; + $result = $command->findBelongsTo($model, []); + $expected = [ + 'belongsTo' => [ + [ + 'alias' => 'Senders', 'foreignKey' => 'sender_id', 'joinType' => 'INNER', + 'className' => 'Users', ], [ - 'alias' => 'Users', + 'alias' => 'Receivers', 'foreignKey' => 'receiver_id', 'joinType' => 'INNER', + 'className' => 'Users', ], ], ]; diff --git a/tests/comparisons/Model/testBakeEntityCustomHidden.php b/tests/comparisons/Model/testBakeEntityCustomHidden.php index 7698be01..8ca1a19e 100644 --- a/tests/comparisons/Model/testBakeEntityCustomHidden.php +++ b/tests/comparisons/Model/testBakeEntityCustomHidden.php @@ -15,6 +15,7 @@ * @property \Cake\I18n\DateTime|null $updated * * @property \Bake\Test\App\Model\Entity\Comment[] $comments + * @property \Bake\Test\App\Model\Entity\Relation[] $relations * @property \Bake\Test\App\Model\Entity\TodoItem[] $todo_items */ class User extends Entity diff --git a/tests/comparisons/Model/testBakeEntityFullContext.php b/tests/comparisons/Model/testBakeEntityFullContext.php index 240761a8..4a4388ea 100644 --- a/tests/comparisons/Model/testBakeEntityFullContext.php +++ b/tests/comparisons/Model/testBakeEntityFullContext.php @@ -15,6 +15,7 @@ * @property \Cake\I18n\DateTime|null $updated * * @property \Bake\Test\App\Model\Entity\Comment[] $comments + * @property \Bake\Test\App\Model\Entity\Relation[] $relations * @property \Bake\Test\App\Model\Entity\TodoItem[] $todo_items */ class User extends Entity @@ -34,6 +35,7 @@ class User extends Entity 'created' => true, 'updated' => true, 'comments' => true, + 'relations' => true, 'todo_items' => true, ]; diff --git a/tests/comparisons/Model/testBakeEntityHidden.php b/tests/comparisons/Model/testBakeEntityHidden.php index ef086579..fe51ce0c 100644 --- a/tests/comparisons/Model/testBakeEntityHidden.php +++ b/tests/comparisons/Model/testBakeEntityHidden.php @@ -15,6 +15,7 @@ * @property \Cake\I18n\DateTime|null $updated * * @property \Bake\Test\App\Model\Entity\Comment[] $comments + * @property \Bake\Test\App\Model\Entity\Relation[] $relations * @property \Bake\Test\App\Model\Entity\TodoItem[] $todo_items */ class User extends Entity diff --git a/tests/comparisons/Model/testBakeEntitySimple.php b/tests/comparisons/Model/testBakeEntitySimple.php index 8a38a690..ee2ae419 100644 --- a/tests/comparisons/Model/testBakeEntitySimple.php +++ b/tests/comparisons/Model/testBakeEntitySimple.php @@ -15,6 +15,7 @@ * @property \Cake\I18n\DateTime|null $updated * * @property \Bake\Test\App\Model\Entity\Comment[] $comments + * @property \Bake\Test\App\Model\Entity\Relation[] $relations * @property \Bake\Test\App\Model\Entity\TodoItem[] $todo_items */ class User extends Entity diff --git a/tests/comparisons/Model/testBakeEntitySimpleUnchanged.php b/tests/comparisons/Model/testBakeEntitySimpleUnchanged.php index 8a38a690..ee2ae419 100644 --- a/tests/comparisons/Model/testBakeEntitySimpleUnchanged.php +++ b/tests/comparisons/Model/testBakeEntitySimpleUnchanged.php @@ -15,6 +15,7 @@ * @property \Cake\I18n\DateTime|null $updated * * @property \Bake\Test\App\Model\Entity\Comment[] $comments + * @property \Bake\Test\App\Model\Entity\Relation[] $relations * @property \Bake\Test\App\Model\Entity\TodoItem[] $todo_items */ class User extends Entity diff --git a/tests/comparisons/Model/testBakeEntityWithPlugin.php b/tests/comparisons/Model/testBakeEntityWithPlugin.php index 4707f601..a9efefbe 100644 --- a/tests/comparisons/Model/testBakeEntityWithPlugin.php +++ b/tests/comparisons/Model/testBakeEntityWithPlugin.php @@ -15,6 +15,7 @@ * @property \Cake\I18n\DateTime|null $updated * * @property \BakeTest\Model\Entity\Comment[] $comments + * @property \BakeTest\Model\Entity\Relation[] $relations * @property \BakeTest\Model\Entity\TodoItem[] $todo_items */ class User extends Entity @@ -34,6 +35,7 @@ class User extends Entity 'created' => true, 'updated' => true, 'comments' => true, + 'relations' => true, 'todo_items' => true, ]; diff --git a/tests/comparisons/Model/testBakeTableWithPlugin.php b/tests/comparisons/Model/testBakeTableWithPlugin.php index 6ca47126..a4797663 100644 --- a/tests/comparisons/Model/testBakeTableWithPlugin.php +++ b/tests/comparisons/Model/testBakeTableWithPlugin.php @@ -12,6 +12,7 @@ * Users Model * * @property \BakeTest\Model\Table\CommentsTable&\Cake\ORM\Association\HasMany $Comments + * @property \BakeTest\Model\Table\RelationsTable&\Cake\ORM\Association\HasMany $Relations * @property \BakeTest\Model\Table\TodoItemsTable&\Cake\ORM\Association\HasMany $TodoItems * * @method \BakeTest\Model\Entity\User newEmptyEntity() @@ -52,6 +53,10 @@ public function initialize(array $config): void 'foreignKey' => 'user_id', 'className' => 'BakeTest.Comments', ]); + $this->hasMany('Relations', [ + 'foreignKey' => 'user_id', + 'className' => 'BakeTest.Relations', + ]); $this->hasMany('TodoItems', [ 'foreignKey' => 'user_id', 'className' => 'BakeTest.TodoItems', diff --git a/tests/comparisons/Model/testBakeWithRulesUnique.php b/tests/comparisons/Model/testBakeWithRulesUnique.php index 775080bf..1c4e92eb 100644 --- a/tests/comparisons/Model/testBakeWithRulesUnique.php +++ b/tests/comparisons/Model/testBakeWithRulesUnique.php @@ -12,6 +12,7 @@ * Users Model * * @property \Bake\Test\App\Model\Table\CommentsTable&\Cake\ORM\Association\HasMany $Comments + * @property \Bake\Test\App\Model\Table\RelationsTable&\Cake\ORM\Association\HasMany $Relations * @property \Bake\Test\App\Model\Table\TodoItemsTable&\Cake\ORM\Association\HasMany $TodoItems * * @method \Bake\Test\App\Model\Entity\User newEmptyEntity() @@ -51,6 +52,9 @@ public function initialize(array $config): void $this->hasMany('Comments', [ 'foreignKey' => 'user_id', ]); + $this->hasMany('Relations', [ + 'foreignKey' => 'user_id', + ]); $this->hasMany('TodoItems', [ 'foreignKey' => 'user_id', ]); diff --git a/tests/schema.php b/tests/schema.php index 561ff0f7..b26ac345 100644 --- a/tests/schema.php +++ b/tests/schema.php @@ -383,6 +383,34 @@ ], 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], ], + [ + 'table' => 'relations', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'user_id' => ['type' => 'integer', 'null' => false], + 'other_id' => ['type' => 'integer', 'null' => false], + 'body' => 'text', + 'created' => 'datetime', + 'updated' => 'datetime', + ], + 'constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + 'user_idx' => [ + 'type' => 'foreign', + 'columns' => ['user_id'], + 'references' => ['users', 'id'], + 'update' => 'noAction', + 'delete' => 'noAction', + ], + 'other_idx' => [ + 'type' => 'foreign', + 'columns' => ['other_id'], + 'references' => ['users', 'id'], + 'update' => 'noAction', + 'delete' => 'noAction', + ], + ], + ], [ 'table' => 'invitations', 'columns' => [ diff --git a/tests/test_app/App/Controller/RelationsController.php b/tests/test_app/App/Controller/RelationsController.php new file mode 100644 index 00000000..14140deb --- /dev/null +++ b/tests/test_app/App/Controller/RelationsController.php @@ -0,0 +1,99 @@ +Relations->find(); + $relations = $this->paginate($query); + + $this->set(compact('relations')); + } + + /** + * View method + * + * @param string|null $id Relation id. + * @return \Cake\Http\Response|null|void Renders view + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function view($id = null) + { + $relation = $this->Relations->get($id, contain: []); + $this->set(compact('relation')); + } + + /** + * Add method + * + * @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise. + */ + public function add() + { + $relation = $this->Relations->newEmptyEntity(); + if ($this->request->is('post')) { + $relation = $this->Relations->patchEntity($relation, $this->request->getData()); + if ($this->Relations->save($relation)) { + $this->Flash->success(__('The relation has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('The relation could not be saved. Please, try again.')); + } + $this->set(compact('relation')); + } + + /** + * Edit method + * + * @param string|null $id Relation id. + * @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise. + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function edit($id = null) + { + $relation = $this->Relations->get($id, contain: []); + if ($this->request->is(['patch', 'post', 'put'])) { + $relation = $this->Relations->patchEntity($relation, $this->request->getData()); + if ($this->Relations->save($relation)) { + $this->Flash->success(__('The relation has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('The relation could not be saved. Please, try again.')); + } + $this->set(compact('relation')); + } + + /** + * Delete method + * + * @param string|null $id Relation id. + * @return \Cake\Http\Response|null Redirects to index. + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function delete($id = null) + { + $this->request->allowMethod(['post', 'delete']); + $relation = $this->Relations->get($id); + if ($this->Relations->delete($relation)) { + $this->Flash->success(__('The relation has been deleted.')); + } else { + $this->Flash->error(__('The relation could not be deleted. Please, try again.')); + } + + return $this->redirect(['action' => 'index']); + } +} From 59219f49915541ddd05faf3ff1fd6a7d9d567115 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 29 Nov 2024 00:44:24 +0100 Subject: [PATCH 11/11] ignore shadow translations tables *_translations --- src/Command/AllCommand.php | 8 +++--- src/Command/FixtureAllCommand.php | 4 ++- src/Command/ModelAllCommand.php | 3 +- src/Command/ModelCommand.php | 4 +-- src/Command/TemplateAllCommand.php | 3 +- src/Utility/TableScanner.php | 31 ++++++++++++++++++--- tests/TestCase/Utility/TableScannerTest.php | 22 +++++++++++++++ 7 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/Command/AllCommand.php b/src/Command/AllCommand.php index 6768c05e..0c046502 100644 --- a/src/Command/AllCommand.php +++ b/src/Command/AllCommand.php @@ -84,17 +84,17 @@ public function execute(Arguments $args, ConsoleIo $io): ?int /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + if (!$name && !$args->getOption('everything')) { $io->out('Choose a table to generate from the following:'); - foreach ($scanner->listUnskipped() as $table) { + foreach ($tables as $table) { $io->out('- ' . $this->_camelize($table)); } return static::CODE_SUCCESS; } - if ($args->getOption('everything')) { - $tables = $scanner->listUnskipped(); - } else { + if (!$args->getOption('everything')) { $tables = [$name]; } diff --git a/src/Command/FixtureAllCommand.php b/src/Command/FixtureAllCommand.php index 32ca856a..40ccdc95 100644 --- a/src/Command/FixtureAllCommand.php +++ b/src/Command/FixtureAllCommand.php @@ -86,7 +86,9 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $connection = ConnectionManager::get($args->getOption('connection') ?? 'default'); $scanner = new TableScanner($connection); $fixture = new FixtureCommand(); - foreach ($scanner->listUnskipped() as $table) { + + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + foreach ($tables as $table) { $fixtureArgs = new Arguments([$table], $args->getOptions(), ['name']); $fixture->execute($fixtureArgs, $io); } diff --git a/src/Command/ModelAllCommand.php b/src/Command/ModelAllCommand.php index e43b4dd7..a65f696c 100644 --- a/src/Command/ModelAllCommand.php +++ b/src/Command/ModelAllCommand.php @@ -83,7 +83,8 @@ public function execute(Arguments $args, ConsoleIo $io): ?int /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); - foreach ($scanner->listUnskipped() as $table) { + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + foreach ($tables as $table) { $this->getTableLocator()->clear(); $modelArgs = new Arguments([$table], $args->getOptions(), ['name']); $this->modelCommand->execute($modelArgs, $io); diff --git a/src/Command/ModelCommand.php b/src/Command/ModelCommand.php index 60c07959..89fb4004 100644 --- a/src/Command/ModelCommand.php +++ b/src/Command/ModelCommand.php @@ -1251,7 +1251,7 @@ public function bakeTable(Table $model, array $data, Arguments $args, ConsoleIo } /** - * Outputs the a list of possible models or controllers from database + * Outputs the list of possible models or controllers from database * * @return array */ @@ -1270,7 +1270,7 @@ public function listAll(): array } /** - * Outputs the a list of unskipped models or controllers from database + * Outputs the list of unskipped models or controllers from database * * @return array */ diff --git a/src/Command/TemplateAllCommand.php b/src/Command/TemplateAllCommand.php index 4be17e5b..9270a81f 100644 --- a/src/Command/TemplateAllCommand.php +++ b/src/Command/TemplateAllCommand.php @@ -65,7 +65,8 @@ public function execute(Arguments $args, ConsoleIo $io): int $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); - foreach ($scanner->listUnskipped() as $table) { + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + foreach ($tables as $table) { $parser = $this->templateCommand->getOptionParser(); $templateArgs = new Arguments( [$table], diff --git a/src/Utility/TableScanner.php b/src/Utility/TableScanner.php index 70f56d15..b10be2c0 100644 --- a/src/Utility/TableScanner.php +++ b/src/Utility/TableScanner.php @@ -58,13 +58,13 @@ public function __construct(Connection $connection, ?array $ignore = null) /** * Get all tables in the connection without applying ignores. * - * @return array + * @return array */ public function listAll(): array { $schema = $this->connection->getSchemaCollection(); $tables = $schema->listTables(); - if (empty($tables)) { + if (!$tables) { throw new RuntimeException('Your database does not have any tables.'); } sort($tables); @@ -75,7 +75,7 @@ public function listAll(): array /** * Get all tables in the connection that aren't ignored. * - * @return array + * @return array */ public function listUnskipped(): array { @@ -90,6 +90,29 @@ public function listUnskipped(): array return $tables; } + /** + * Call from any All command that needs the shadow translation tables to be skipped. + * + * @param array $tables + * @return array + */ + public function removeShadowTranslationTables(array $tables): array + { + foreach ($tables as $key => $table) { + if (!preg_match('/^(.+)_translations$/', $table, $matches)) { + continue; + } + + if (empty($tables[$matches[1]])) { + continue; + } + + unset($tables[$key]); + } + + return $tables; + } + /** * @param string $table Table name. * @return bool @@ -97,7 +120,7 @@ public function listUnskipped(): array protected function shouldSkip(string $table): bool { foreach ($this->ignore as $ignore) { - if (strpos($ignore, '/') === 0) { + if (str_starts_with($ignore, '/')) { if ((bool)preg_match($ignore, $table)) { return true; } diff --git a/tests/TestCase/Utility/TableScannerTest.php b/tests/TestCase/Utility/TableScannerTest.php index ea17744a..4591032e 100644 --- a/tests/TestCase/Utility/TableScannerTest.php +++ b/tests/TestCase/Utility/TableScannerTest.php @@ -125,4 +125,26 @@ public function testListUnskippedRegex() } } } + + /** + * @return void + */ + public function testRemoveShadowTranslationTables(): void + { + $this->tableScanner = new TableScanner($this->connection); + + $tables = [ + 'items' => 'items', + 'users' => 'users', + 'users_translations' => 'users_translations', + 'item_translations' => 'item_translations', + ]; + $result = $this->tableScanner->removeShadowTranslationTables($tables); + $expected = [ + 'items' => 'items', + 'users' => 'users', + 'item_translations' => 'item_translations', + ]; + $this->assertEquals($expected, $result); + } }