diff --git a/.phive/phars.xml b/.phive/phars.xml
index 726b7777c..d311bfa5e 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,5 +1,5 @@
-
-
+
+
diff --git a/composer.json b/composer.json
index f7a4a2d07..20cbc0d3b 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"
},
diff --git a/docs/en/development.rst b/docs/en/development.rst
index dd66b99f0..c7c7aa252 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/en/usage.rst b/docs/en/usage.rst
index 29da6dcba..8cf9da928 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
===========
diff --git a/docs/fr/development.rst b/docs/fr/development.rst
index f9a24c128..697a6301d 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/phpstan-baseline.neon b/phpstan-baseline.neon
index 091e0a301..e45840cfa 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 9e4919042..bb91af0d8 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;
}
diff --git a/src/Command/AllCommand.php b/src/Command/AllCommand.php
index 34eeeeeb5..0c0465021 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,20 +84,21 @@ 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')) {
+ $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];
}
+ $errors = 0;
foreach ($this->commands as $commandName) {
/** @var \Cake\Command\Command $command */
$command = new $commandName();
@@ -113,12 +115,27 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
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;
}
}
diff --git a/src/Command/FixtureAllCommand.php b/src/Command/FixtureAllCommand.php
index 32ca856a5..40ccdc959 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 e43b4dd71..a65f696cd 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 423f088e8..89fb40043 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;
}
@@ -1243,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
*/
@@ -1262,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
*/
@@ -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/src/Command/PluginCommand.php b/src/Command/PluginCommand.php
index 6ca6c694d..053006aaa 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/src/Command/TemplateAllCommand.php b/src/Command/TemplateAllCommand.php
index 4be17e5b4..9270a81ff 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 70f56d15e..b10be2c0e 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/templates/bake/Command/command.twig b/templates/bake/Command/command.twig
index fe126f6c7..3654c3341 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/templates/bake/Plugin/src/Plugin.php.twig b/templates/bake/Plugin/src/Plugin.php.twig
index a7d1575ac..e1982d0f2 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);
@@ -99,10 +103,11 @@ 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
{
// Add your services here
+ // remove this method hook if you don't need it
}
}
diff --git a/tests/Fixture/RelationsFixture.php b/tests/Fixture/RelationsFixture.php
new file mode 100644
index 000000000..d7b09a1f3
--- /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 c63f54962..91cce3a30 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/TestCase/Command/PluginCommandTest.php b/tests/TestCase/Command/PluginCommandTest.php
index d657730e7..4936f638f 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/TestCase/Utility/TableScannerTest.php b/tests/TestCase/Utility/TableScannerTest.php
index ea17744a5..4591032e4 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);
+ }
}
diff --git a/tests/comparisons/Command/testBakePlugin.php b/tests/comparisons/Command/testBakePlugin.php
index c762139d6..da518ce27 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());
}
/**
diff --git a/tests/comparisons/Model/testBakeEntityCustomHidden.php b/tests/comparisons/Model/testBakeEntityCustomHidden.php
index 7698be017..8ca1a19ef 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 240761a8a..4a4388eae 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 ef0865792..fe51ce0cd 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 8a38a6902..ee2ae4198 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 8a38a6902..ee2ae4198 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 4707f6010..a9efefbe1 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 6ca471261..a47976633 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 775080bfc..1c4e92eb5 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/comparisons/Plugin/Company/Example/src/ExamplePlugin.php b/tests/comparisons/Plugin/Company/Example/src/ExamplePlugin.php
index 1d9470854..197dbdf83 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);
@@ -84,10 +88,11 @@ 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
{
// Add your services here
+ // remove this method hook if you don't need it
}
}
diff --git a/tests/comparisons/Plugin/SimpleExample/.gitignore b/tests/comparisons/Plugin/SimpleExample/.gitignore
deleted file mode 100644
index 244d127b1..000000000
--- 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 889409c70..000000000
--- 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 9a29c6357..000000000
--- 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 3d4672947..000000000
--- a/tests/comparisons/Plugin/SimpleExample/phpunit.xml.dist
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- tests/TestCase/
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php b/tests/comparisons/Plugin/SimpleExample/src/SimpleExamplePlugin.php
index 45bbf4908..d53ed8b11 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);
@@ -84,10 +88,11 @@ 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
{
// Add your services here
+ // remove this method hook if you don't need it
}
}
diff --git a/tests/comparisons/Plugin/SimpleExample/tests/bootstrap.php b/tests/comparisons/Plugin/SimpleExample/tests/bootstrap.php
deleted file mode 100644
index 445c88c04..000000000
--- 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 e569a9608..000000000
--- a/tests/comparisons/Plugin/SimpleExample/tests/schema.sql
+++ /dev/null
@@ -1 +0,0 @@
--- Test database schema for SimpleExample
diff --git a/tests/schema.php b/tests/schema.php
index 561ff0f71..b26ac345e 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 000000000..14140deb3
--- /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']);
+ }
+}