diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 167cf4e46..5282b6915 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,7 +11,7 @@ on:
jobs:
testsuite:
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
@@ -151,7 +151,7 @@ jobs:
cs-stan:
name: Coding Standard & Static Analysis
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
@@ -168,7 +168,7 @@ jobs:
run: composer stan-setup
- name: Run phpcs
- run: vendor/bin/phpcs --report=checkstyle src/ tests/ | cs2pr
+ run: vendor/bin/phpcs --report=checkstyle | cs2pr
- name: Run psalm
if: success() || failure()
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 000000000..69e7d139d
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,29 @@
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+permissions:
+ contents: read
+
+jobs:
+ stale:
+
+ permissions:
+ issues: write # for actions/stale to close stale issues
+ pull-requests: write # for actions/stale to close stale PRs
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/stale@v8
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. Remove the `stale` label or comment or this will be closed in 15 days'
+ stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Remove the `stale` label or comment on this issue, or it will be closed in 15 days'
+ stale-issue-label: 'stale'
+ stale-pr-label: 'stale'
+ days-before-stale: 120
+ days-before-close: 15
+ exempt-issue-labels: 'pinned'
+ exempt-pr-labels: 'pinned'
diff --git a/composer.json b/composer.json
index 58ad76d07..7dda5014e 100644
--- a/composer.json
+++ b/composer.json
@@ -50,9 +50,9 @@
"@test",
"@cs-check"
],
- "cs-check": "phpcs --parallel=16 -p src/ tests/",
- "cs-fix": "phpcbf --parallel=16 -p src/ tests/",
- "stan": "phpstan analyse src/ && psalm.phar",
+ "cs-check": "phpcs -p --parallel=16",
+ "cs-fix": "phpcbf -p --parallel=16",
+ "stan": "phpstan analyse && psalm.phar",
"stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^1.7 psalm/phar:~4.27.0 && mv composer.backup composer.json",
"test": "phpunit",
"test-coverage": "phpunit --coverage-clover=clover.xml"
diff --git a/docs.Dockerfile b/docs.Dockerfile
index 1f7afabdc..4f3ca473c 100644
--- a/docs.Dockerfile
+++ b/docs.Dockerfile
@@ -1,5 +1,5 @@
# Generate the HTML output.
-FROM markstory/cakephp-docs-builder as builder
+FROM ghcr.io/cakephp/docs-builder as builder
# Copy entire repo in with .git so we can build all versions in one image.
COPY docs /data/docs
@@ -8,7 +8,7 @@ RUN cd /data/docs-builder \
&& make website LANGS="en es fr ja pt ru" SOURCE=/data/docs DEST=/data/website/
# Build a small nginx container with just the static site in it.
-FROM markstory/cakephp-docs-builder:runtime as runtime
+FROM ghcr.io/cakephp/docs-builder:runtime as runtime
# Configure search index script
ENV LANGS="en es fr ja pt ru"
diff --git a/docs/en/development.rst b/docs/en/development.rst
index 735c4183f..2efb76b2a 100644
--- a/docs/en/development.rst
+++ b/docs/en/development.rst
@@ -226,7 +226,7 @@ dependencies in your templates you can include template overrides in your
application templates. These overrides work similar to overriding other plugin
templates.
-#. Create a new directory **/templates/plugin/Bake/**.
+#. Create a new directory **/templates/plugin/Bake/bake/**.
#. Copy any templates you want to override from
**vendor/cakephp/bake/templates/bake/** to matching files in your
application.
diff --git a/phpcs.xml.dist b/phpcs.xml
similarity index 52%
rename from phpcs.xml.dist
rename to phpcs.xml
index ce02df237..dda905e36 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml
@@ -1,8 +1,14 @@
+
+
+ src/
+ tests/
*/comparisons/*
+ /tests/test_app/tests/
+ /tests/test_app/Plugin/TestBake/
diff --git a/src/Command/ModelCommand.php b/src/Command/ModelCommand.php
index 8c72cef32..fc9b95ec7 100644
--- a/src/Command/ModelCommand.php
+++ b/src/Command/ModelCommand.php
@@ -233,7 +233,7 @@ public function getAssociations(Table $table, Arguments $args, ConsoleIo $io): a
];
$primary = $table->getPrimaryKey();
- $associations = $this->findBelongsTo($table, $associations);
+ $associations = $this->findBelongsTo($table, $associations, $args);
if (is_array($primary) && count($primary) > 1) {
$io->warning(
@@ -329,9 +329,10 @@ public function getAssociationInfo(Table $table): array
*
* @param \Cake\ORM\Table $model Database\Table instance of table being generated.
* @param array $associations Array of in progress associations
+ * @param \Cake\Console\Arguments|null $args CLI arguments
* @return array Associations with belongsTo added in.
*/
- public function findBelongsTo(Table $model, array $associations): array
+ public function findBelongsTo(Table $model, array $associations, ?Arguments $args = null): array
{
$schema = $model->getSchema();
foreach ($schema->columns() as $fieldName) {
@@ -362,11 +363,13 @@ public function findBelongsTo(Table $model, array $associations): array
get_class($associationTable) === Table::class &&
!in_array(Inflector::tableize($tmpModelName), $tables, true)
) {
+ $allowAliasRelations = $args && $args->getOption('skip-relation-check');
$found = $this->findTableReferencedBy($schema, $fieldName);
- if (!$found) {
+ if ($found) {
+ $tmpModelName = Inflector::camelize($found);
+ } elseif (!$allowAliasRelations) {
continue;
}
- $tmpModelName = Inflector::camelize($found);
}
$assoc = [
'alias' => $tmpModelName,
@@ -1319,6 +1322,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
])->addOption('no-fixture', [
'boolean' => true,
'help' => 'Do not generate a test fixture skeleton.',
+ ])->addOption('skip-relation-check', [
+ 'boolean' => true,
+ 'help' => 'Generate relations for all "example_id" fields'
+ . ' without checking the database if a table "examples" exists.',
])->setEpilog(
'Omitting all arguments and options will list the table names you can generate models for.'
);
diff --git a/src/Command/PluginCommand.php b/src/Command/PluginCommand.php
index 5e12bb471..992ba2ee9 100644
--- a/src/Command/PluginCommand.php
+++ b/src/Command/PluginCommand.php
@@ -383,7 +383,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
])->addOption('theme', [
'short' => 't',
'help' => 'The theme to use when baking code.',
- 'default' => Configure::read('Bake.theme') ?? '',
+ 'default' => Configure::read('Bake.theme') ?: null,
'choices' => $this->_getBakeThemes(),
]);
diff --git a/src/View/BakeView.php b/src/View/BakeView.php
index bc818f08c..3a3b6df78 100644
--- a/src/View/BakeView.php
+++ b/src/View/BakeView.php
@@ -107,7 +107,7 @@ public function render(?string $template = null, $layout = null): string
*
* @param mixed $subject The object that this event applies to
* ($this by default).
- * @return \Cake\Event\EventInterface
+ * @return \Cake\Event\EventInterface
*/
public function dispatchEvent(string $name, $data = null, $subject = null): EventInterface
{
diff --git a/tests/TestCase/Command/ModelCommandTest.php b/tests/TestCase/Command/ModelCommandTest.php
index e7c5c6791..3080f58e7 100644
--- a/tests/TestCase/Command/ModelCommandTest.php
+++ b/tests/TestCase/Command/ModelCommandTest.php
@@ -490,6 +490,59 @@ public function testGetAssociationsAddAssociationIfTableExist()
$this->assertEquals($expected, $result);
}
+ /**
+ * Test that association generation adds `Anythings` association for `anything_id` field
+ * when using `--skip-relation-check` option, even if no db table exists
+ *
+ * @return void
+ */
+ public function testGetAssociationsAddAssociationIfNoTableExistButAliasIsAllowed()
+ {
+ $items = $this->getTableLocator()->get('TodoItems');
+
+ $items->setSchema($items->getSchema()->addColumn('anything_id', ['type' => 'integer']));
+ $command = new ModelCommand();
+ $command->connection = 'test';
+
+ $args = new Arguments([], ['skip-relation-check' => true], []);
+ $io = $this->createMock(ConsoleIo::class);
+ $result = $command->getAssociations($items, $args, $io);
+ $expected = [
+ 'belongsTo' => [
+ [
+ 'alias' => 'Users',
+ 'foreignKey' => 'user_id',
+ 'joinType' => 'INNER',
+ ],
+ [
+ 'alias' => 'Anythings',
+ 'foreignKey' => 'anything_id',
+ ],
+ ],
+ 'hasMany' => [
+ [
+ 'alias' => 'TodoTasks',
+ 'foreignKey' => 'todo_item_id',
+ ],
+ ],
+ 'belongsToMany' => [
+ [
+ 'alias' => 'TodoLabels',
+ 'foreignKey' => 'todo_item_id',
+ 'joinTable' => 'todo_items_todo_labels',
+ 'targetForeignKey' => 'todo_label_id',
+ ],
+ ],
+ 'hasOne' => [
+ [
+ 'alias' => 'TodoReminders',
+ 'foreignKey' => 'todo_item_id',
+ ],
+ ],
+ ];
+ $this->assertEquals($expected, $result);
+ }
+
/**
* Test that association generation ignores `_id` fields
*
diff --git a/tests/TestCase/Command/PluginCommandTest.php b/tests/TestCase/Command/PluginCommandTest.php
index 03d0858f8..5aab14ebb 100644
--- a/tests/TestCase/Command/PluginCommandTest.php
+++ b/tests/TestCase/Command/PluginCommandTest.php
@@ -34,6 +34,8 @@ class PluginCommandTest extends TestCase
{
protected $testAppFile = APP . 'Application.php';
+ protected $pluginsPath = TMP . 'plugin_task' . DS;
+
/**
* setUp method
*
@@ -47,11 +49,10 @@ public function setUp(): void
$this->useCommandRunner();
// Output into a safe place.
- $path = TMP . 'plugin_task' . DS;
- Configure::write('App.paths.plugins', [$path]);
+ Configure::write('App.paths.plugins', [$this->pluginsPath]);
// Create the test output path
- mkdir($path, 0777, true);
+ mkdir($this->pluginsPath, 0777, true);
if (file_exists(APP . 'Application.php.bak')) {
rename(APP . 'Application.php.bak', APP . 'Application.php');
@@ -68,7 +69,7 @@ public function setUp(): void
public function tearDown(): void
{
$fs = new Filesystem();
- $fs->deleteDir(TMP . 'plugin_task');
+ $fs->deleteDir($this->pluginsPath);
if (file_exists(APP . 'Application.php.bak')) {
rename(APP . 'Application.php.bak', APP . 'Application.php');
@@ -89,6 +90,16 @@ public function testMainBakePluginContents()
$this->assertPluginContents('SimpleExample');
}
+ public function testBakingWithNonExistentPluginsDir()
+ {
+ $fs = new Filesystem();
+ $fs->deleteDir($this->pluginsPath);
+
+ $this->exec('bake plugin SimpleExample', ['y', 'n']);
+ $this->assertExitCode(CommandInterface::CODE_SUCCESS);
+ $this->assertPluginContents('SimpleExample');
+ }
+
/**
* test creating a plugin with a custom app namespace.
*
@@ -211,7 +222,7 @@ public function testFindPathNonExistent()
$result = $command->findPath($paths, $io);
$this->assertNull($result, 'no return');
- $this->assertSame(TMP . 'plugin_task' . DS, $command->path);
+ $this->assertSame($this->pluginsPath, $command->path);
}
/**