Skip to content

Commit

Permalink
Merge pull request #1023 from cakephp/3.next-merge
Browse files Browse the repository at this point in the history
merge 3.x => 3.next
  • Loading branch information
ADmad authored Dec 14, 2024
2 parents db037f5 + 8673a0d commit 27356b4
Show file tree
Hide file tree
Showing 39 changed files with 497 additions and 200 deletions.
4 changes: 2 additions & 2 deletions .phive/phars.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpstan" version="1.11.9" installed="1.11.9" location="./tools/phpstan" copy="false"/>
<phar name="psalm" version="5.25.0" installed="5.25.0" location="./tools/psalm" copy="false"/>
<phar name="phpstan" version="2.0.1" installed="2.0.1" location="./tools/phpstan" copy="false"/>
<phar name="psalm" version="5.26.1" installed="5.26.1" location="./tools/psalm" copy="false"/>
</phive>
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
4 changes: 2 additions & 2 deletions docs/en/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down
12 changes: 12 additions & 0 deletions docs/en/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
===========

Expand Down
4 changes: 2 additions & 2 deletions docs/fr/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down
12 changes: 10 additions & 2 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -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
7 changes: 2 additions & 5 deletions src/BakePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
}

Expand Down
35 changes: 26 additions & 9 deletions src/Command/AllCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Datasource\ConnectionManager;
use Throwable;

/**
* Command for `bake all`
Expand Down Expand Up @@ -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', [
Expand Down Expand Up @@ -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();
Expand All @@ -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('<error>' . $message . '</error>');
$errors++;
}
}
}

$io->out('<success>Bake All complete.</success>', 1, ConsoleIo::NORMAL);
if ($errors) {
$io->out(sprintf('<warning>Bake All completed, but with %s errors.</warning>', $errors), 1, ConsoleIo::NORMAL);
} else {
$io->out('<success>Bake All complete.</success>', 1, ConsoleIo::NORMAL);
}

return static::CODE_SUCCESS;
return $errors ? static::CODE_ERROR : static::CODE_SUCCESS;
}
}
4 changes: 3 additions & 1 deletion src/Command/FixtureAllCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Command/ModelAllCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
53 changes: 50 additions & 3 deletions src/Command/ModelCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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'];
Expand Down Expand Up @@ -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 = [
Expand All @@ -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;
}
Expand All @@ -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';
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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<string>
*/
Expand All @@ -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<string>
*/
Expand Down Expand Up @@ -1546,4 +1554,43 @@ protected function bakeEnums(Table $model, array $data, Arguments $args, Console
$enumCommand->execute($args, $io);
}
}

/**
* @param array<string, array<string, mixed>> $associations
* @return array<string, array<string, mixed>>
*/
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<string, mixed> $association
* @return string
*/
protected function createAssociationAlias(array $association): string
{
$foreignKey = $association['foreignKey'];

return $this->_modelNameFromKey($foreignKey);
}
}
Loading

0 comments on commit 27356b4

Please sign in to comment.