Skip to content

Commit 8d03e28

Browse files
committed
Merge branch '3.x' into 3.next
2 parents 436cdf4 + 7a9b75a commit 8d03e28

File tree

9 files changed

+349
-17
lines changed

9 files changed

+349
-17
lines changed

.github/workflows/deploy_docs_2x.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- name: Cloning repo
15-
uses: actions/checkout@v5
15+
uses: actions/checkout@v6
1616
with:
1717
fetch-depth: 0
1818

.github/workflows/deploy_docs_3x.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- name: Cloning repo
15-
uses: actions/checkout@v5
15+
uses: actions/checkout@v6
1616
with:
1717
fetch-depth: 0
1818

.phive/phars.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phive xmlns="https://phar.io/phive">
3-
<phar name="phpstan" version="2.1.17" installed="2.1.17" location="./tools/phpstan" copy="false"/>
3+
<phar name="phpstan" version="2.1.33" installed="2.1.33" location="./tools/phpstan" copy="false"/>
44
</phive>

src/Command/TestCommand.php

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class TestCommand extends BakeCommand
5555
'Command' => 'Command',
5656
'CommandHelper' => 'Command\Helper',
5757
'Middleware' => 'Middleware',
58+
'Class' => '',
5859
];
5960

6061
/**
@@ -75,6 +76,7 @@ class TestCommand extends BakeCommand
7576
'Command' => 'Command',
7677
'CommandHelper' => 'Helper',
7778
'Middleware' => 'Middleware',
79+
'Class' => '',
7880
];
7981

8082
/**
@@ -123,7 +125,11 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
123125
$name = $args->getArgument('name');
124126
$name = $this->_getName($name);
125127

126-
if ($this->bake($type, $name, $args, $io)) {
128+
$result = $this->bake($type, $name, $args, $io);
129+
if ($result === static::CODE_ERROR) {
130+
return static::CODE_ERROR;
131+
}
132+
if ($result) {
127133
$io->success('Done');
128134
}
129135

@@ -212,10 +218,29 @@ protected function _getClassOptions(string $namespace): array
212218
}
213219

214220
$path = $base . str_replace('\\', DS, $namespace);
215-
$files = (new Filesystem())->find($path);
216-
foreach ($files as $fileObj) {
217-
if ($fileObj->isFile()) {
218-
$classes[] = substr($fileObj->getFileName(), 0, -4) ?: '';
221+
222+
// For generic Class type (empty namespace), search recursively
223+
if ($namespace === '') {
224+
$files = (new Filesystem())->findRecursive($path, '/\.php$/');
225+
foreach ($files as $fileObj) {
226+
if ($fileObj->isFile() && $fileObj->getFileName() !== 'Application.php') {
227+
// Build the namespace path relative to App directory
228+
$relativePath = str_replace($base, '', $fileObj->getPath());
229+
$relativePath = trim(str_replace(DS, '\\', $relativePath), '\\');
230+
$className = substr($fileObj->getFileName(), 0, -4) ?: '';
231+
if ($relativePath) {
232+
$classes[] = $relativePath . '\\' . $className;
233+
} else {
234+
$classes[] = $className;
235+
}
236+
}
237+
}
238+
} else {
239+
$files = (new Filesystem())->find($path);
240+
foreach ($files as $fileObj) {
241+
if ($fileObj->isFile()) {
242+
$classes[] = substr($fileObj->getFileName(), 0, -4) ?: '';
243+
}
219244
}
220245
}
221246
sort($classes);
@@ -230,18 +255,43 @@ protected function _getClassOptions(string $namespace): array
230255
* @param string $className the 'cake name' for the class ie. Posts for the PostsController
231256
* @param \Cake\Console\Arguments $args Arguments
232257
* @param \Cake\Console\ConsoleIo $io ConsoleIo instance
233-
* @return string|bool
258+
* @return string|bool|int Returns the generated code as string on success, false on failure, or CODE_ERROR for validation errors
234259
*/
235-
public function bake(string $type, string $className, Arguments $args, ConsoleIo $io): string|bool
260+
public function bake(string $type, string $className, Arguments $args, ConsoleIo $io): string|bool|int
236261
{
237262
$type = $this->normalize($type);
238263
if (!isset($this->classSuffixes[$type]) || !isset($this->classTypes[$type])) {
239264
return false;
240265
}
241266

267+
// For Class type, validate that backslashes are properly escaped
268+
if ($type === 'Class' && !str_contains($className, '\\')) {
269+
$io->error('Class name appears to have no namespace separators.');
270+
$io->out('');
271+
$io->out('If you meant to specify a namespaced class, please use quotes:');
272+
$io->out(" <info>bin/cake bake test class '{$className}'</info>");
273+
$io->out('');
274+
$io->out('Or specify without the base namespace:');
275+
$io->out(' <info>bin/cake bake test class YourNamespace\\ClassName</info>');
276+
277+
return static::CODE_ERROR;
278+
}
279+
242280
$prefix = $this->getPrefix($args);
243281
$fullClassName = $this->getRealClassName($type, $className, $prefix);
244282

283+
// For Class type, validate that the class exists
284+
if ($type === 'Class' && !class_exists($fullClassName)) {
285+
$io->error("Class '{$fullClassName}' does not exist or cannot be loaded.");
286+
$io->out('');
287+
$io->out('Please check:');
288+
$io->out(' - The class file exists in the correct location');
289+
$io->out(' - The class is properly autoloaded');
290+
$io->out(' - The namespace and class name are correct');
291+
292+
return static::CODE_ERROR;
293+
}
294+
245295
// Check if fixture factories plugin is available
246296
$hasFixtureFactories = $this->hasFixtureFactories();
247297

@@ -266,8 +316,14 @@ public function bake(string $type, string $className, Arguments $args, ConsoleIo
266316
[$preConstruct, $construction, $postConstruct] = $this->generateConstructor($type, $fullClassName);
267317
$uses = $this->generateUses($type, $fullClassName);
268318

269-
$subject = $className;
270-
[$namespace, $className] = namespaceSplit($fullClassName);
319+
// For generic Class type, extract just the class name for the subject
320+
if ($type === 'Class') {
321+
[$namespace, $className] = namespaceSplit($fullClassName);
322+
$subject = $className;
323+
} else {
324+
$subject = $className;
325+
[$namespace, $className] = namespaceSplit($fullClassName);
326+
}
271327

272328
$baseNamespace = Configure::read('App.namespace');
273329
if ($this->plugin) {
@@ -381,6 +437,17 @@ public function getRealClassName(string $type, string $class, ?string $prefix =
381437
if ($this->plugin) {
382438
$namespace = str_replace('/', '\\', $this->plugin);
383439
}
440+
441+
// For generic Class type, the class name contains the full subnamespace path
442+
if ($type === 'Class') {
443+
// Strip base namespace if user included it
444+
if (str_starts_with($class, $namespace . '\\')) {
445+
$class = substr($class, strlen($namespace) + 1);
446+
}
447+
448+
return $namespace . '\\' . $class;
449+
}
450+
384451
$suffix = $this->classSuffixes[$type];
385452
$subSpace = $this->mapType($type);
386453
if ($suffix && strpos($class, $suffix) === false) {
@@ -415,7 +482,7 @@ public function getSubspacePath(string $type): string
415482
*/
416483
public function mapType(string $type): string
417484
{
418-
if (empty($this->classTypes[$type])) {
485+
if (!isset($this->classTypes[$type])) {
419486
throw new CakeException('Invalid object type: ' . $type);
420487
}
421488

@@ -585,6 +652,18 @@ public function generateConstructor(string $type, string $fullClassName): array
585652
$pre .= ' $this->io = new ConsoleIo($this->stub);';
586653
$construct = "new {$className}(\$this->io);";
587654
}
655+
if ($type === 'Class') {
656+
// Check if class has required constructor parameters
657+
if (class_exists($fullClassName)) {
658+
$reflection = new ReflectionClass($fullClassName);
659+
$constructor = $reflection->getConstructor();
660+
if (!$constructor || $constructor->getNumberOfRequiredParameters() === 0) {
661+
$construct = "new {$className}();";
662+
}
663+
} else {
664+
$construct = "new {$className}();";
665+
}
666+
}
588667

589668
return [$pre, $construct, $post];
590669
}
@@ -635,7 +714,17 @@ public function generateProperties(string $type, string $subject, string $fullCl
635714
break;
636715
}
637716

638-
if (!in_array($type, ['Controller', 'Command'])) {
717+
// Skip test subject property for Controller, Command, and Class types with required constructor params
718+
$skipProperty = in_array($type, ['Controller', 'Command'], true);
719+
if ($type === 'Class' && class_exists($fullClassName)) {
720+
$reflection = new ReflectionClass($fullClassName);
721+
$constructor = $reflection->getConstructor();
722+
if ($constructor && $constructor->getNumberOfRequiredParameters() > 0) {
723+
$skipProperty = true;
724+
}
725+
}
726+
727+
if (!$skipProperty) {
639728
$properties[] = [
640729
'description' => 'Test subject',
641730
'type' => '\\' . $fullClassName,

src/Utility/Model/AssociationFilter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public function filterAssociations(Table $model): array
9393
}
9494

9595
try {
96+
$foreignKey = (array)$assoc->getForeignKey();
9697
$associations[$type][$assocName] = [
9798
'property' => $assoc->getProperty(),
9899
'variable' => Inflector::variable($assocName),
@@ -101,7 +102,7 @@ public function filterAssociations(Table $model): array
101102
'foreignKey' => $assoc->getForeignKey(),
102103
'alias' => $alias,
103104
'controller' => $className,
104-
'fields' => $target->getSchema()->columns(),
105+
'fields' => array_values(array_diff($target->getSchema()->columns(), $foreignKey)),
105106
'navLink' => $navLink,
106107
];
107108
} catch (Exception $e) {

0 commit comments

Comments
 (0)