Skip to content

Commit 436cdf4

Browse files
committed
Merge branch '3.x' into 3.next
2 parents 02a43ee + 27b1c18 commit 436cdf4

File tree

6 files changed

+438
-3
lines changed

6 files changed

+438
-3
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"php": ">=8.1",
2626
"brick/varexporter": "^0.6.0",
2727
"cakephp/cakephp": "^5.1",
28-
"cakephp/twig-view": "^2.0.0",
28+
"cakephp/twig-view": "^2.0.2",
2929
"nikic/php-parser": "^5.0.0"
3030
},
3131
"require-dev": {
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 3.0.0
15+
* @license https://www.opensource.org/licenses/mit-license.php MIT License
16+
*/
17+
namespace Bake\CodeGen;
18+
19+
use Exception;
20+
use PhpParser\Node;
21+
use PhpParser\Node\Expr\MethodCall;
22+
use PhpParser\Node\Expr\Variable;
23+
use PhpParser\Node\Stmt\Expression;
24+
use PhpParser\NodeTraverser;
25+
use PhpParser\NodeVisitorAbstract;
26+
use PhpParser\Parser;
27+
use PhpParser\ParserFactory;
28+
use PhpParser\PhpVersion;
29+
30+
/**
31+
* Extracts column type mappings from existing Table class initialize methods.
32+
*
33+
* @internal
34+
*/
35+
class ColumnTypeExtractor extends NodeVisitorAbstract
36+
{
37+
/**
38+
* @var \PhpParser\Parser
39+
*/
40+
protected Parser $parser;
41+
42+
/**
43+
* @var array<string, string>
44+
*/
45+
protected array $columnTypes = [];
46+
47+
/**
48+
* @var bool
49+
*/
50+
protected bool $inInitialize = false;
51+
52+
/**
53+
* Constructor
54+
*/
55+
public function __construct()
56+
{
57+
$version = PhpVersion::fromComponents(8, 1);
58+
$this->parser = (new ParserFactory())->createForVersion($version);
59+
}
60+
61+
/**
62+
* Extracts column type mappings from initialize method code
63+
*
64+
* @param string $code The initialize method code
65+
* @return array<string, string> Map of column names to type expressions
66+
*/
67+
public function extract(string $code): array
68+
{
69+
$this->columnTypes = [];
70+
$this->inInitialize = false;
71+
72+
try {
73+
// Wrap code in a dummy class if needed for parsing
74+
$wrappedCode = "<?php\nclass Dummy {\n" . $code . "\n}";
75+
$ast = $this->parser->parse($wrappedCode);
76+
77+
$traverser = new NodeTraverser();
78+
$traverser->addVisitor($this);
79+
$traverser->traverse($ast);
80+
} catch (Exception $e) {
81+
// If parsing fails, return empty array
82+
return [];
83+
}
84+
85+
return $this->columnTypes;
86+
}
87+
88+
/**
89+
* @inheritDoc
90+
*/
91+
public function enterNode(Node $node)
92+
{
93+
// Check if we're entering the initialize method
94+
if ($node instanceof Node\Stmt\ClassMethod && $node->name->name === 'initialize') {
95+
$this->inInitialize = true;
96+
97+
return null;
98+
}
99+
100+
// Only process nodes within initialize method
101+
if (!$this->inInitialize) {
102+
return null;
103+
}
104+
105+
// Look for $this->getSchema()->setColumnType() calls
106+
if ($node instanceof Expression && $node->expr instanceof MethodCall) {
107+
$this->processMethodCall($node->expr);
108+
} elseif ($node instanceof MethodCall) {
109+
$this->processMethodCall($node);
110+
}
111+
112+
return null;
113+
}
114+
115+
/**
116+
* @inheritDoc
117+
*/
118+
public function leaveNode(Node $node)
119+
{
120+
if ($node instanceof Node\Stmt\ClassMethod && $node->name->name === 'initialize') {
121+
$this->inInitialize = false;
122+
}
123+
124+
return null;
125+
}
126+
127+
/**
128+
* Process a method call to check if it's setColumnType
129+
*
130+
* @param \PhpParser\Node\Expr\MethodCall $methodCall The method call to process
131+
* @return void
132+
*/
133+
protected function processMethodCall(MethodCall $methodCall): void
134+
{
135+
// Check if this is a setColumnType call
136+
if ($methodCall->name instanceof Node\Identifier && $methodCall->name->name === 'setColumnType') {
137+
// Check if it's called on getSchema()
138+
if (
139+
$methodCall->var instanceof MethodCall &&
140+
$methodCall->var->name instanceof Node\Identifier &&
141+
$methodCall->var->name->name === 'getSchema' &&
142+
$methodCall->var->var instanceof Variable &&
143+
$methodCall->var->var->name === 'this'
144+
) {
145+
// Extract the column name and type expression
146+
if (count($methodCall->args) >= 2) {
147+
$columnArg = $methodCall->args[0]->value;
148+
$typeArg = $methodCall->args[1]->value;
149+
150+
// Get column name
151+
$columnName = $this->getStringValue($columnArg);
152+
if ($columnName === null) {
153+
return;
154+
}
155+
156+
// Get the type expression as a string
157+
$typeExpression = $this->getTypeExpression($typeArg);
158+
if ($typeExpression !== null) {
159+
$this->columnTypes[$columnName] = $typeExpression;
160+
}
161+
}
162+
}
163+
}
164+
}
165+
166+
/**
167+
* Get string value from a node
168+
*
169+
* @param \PhpParser\Node $node The node to extract string from
170+
* @return string|null The string value or null
171+
*/
172+
protected function getStringValue(Node $node): ?string
173+
{
174+
if ($node instanceof Node\Scalar\String_) {
175+
return $node->value;
176+
}
177+
178+
return null;
179+
}
180+
181+
/**
182+
* Convert a type expression node to string representation
183+
*
184+
* @param \PhpParser\Node $node The type expression node
185+
* @return string|null String representation of the type expression
186+
*/
187+
protected function getTypeExpression(Node $node): ?string
188+
{
189+
// Handle EnumType::from() calls
190+
if (
191+
$node instanceof Node\Expr\StaticCall &&
192+
$node->class instanceof Node\Name &&
193+
$node->name instanceof Node\Identifier
194+
) {
195+
$className = $node->class->toString();
196+
$methodName = $node->name->name;
197+
198+
// Handle EnumType::from() calls
199+
if ($className === 'EnumType' || str_ends_with($className, '\\EnumType')) {
200+
if ($methodName === 'from' && count($node->args) > 0) {
201+
// Extract the enum class name
202+
$arg = $node->args[0]->value;
203+
if ($arg instanceof Node\Expr\ClassConstFetch) {
204+
if (
205+
$arg->class instanceof Node\Name &&
206+
$arg->name instanceof Node\Identifier &&
207+
$arg->name->name === 'class'
208+
) {
209+
$enumClass = $arg->class->toString();
210+
// Return the full EnumType::from() expression
211+
return 'EnumType::from(' . $enumClass . '::class)';
212+
}
213+
}
214+
}
215+
}
216+
}
217+
218+
// Handle simple string types
219+
if ($node instanceof Node\Scalar\String_) {
220+
return '"' . $node->value . '"';
221+
}
222+
223+
return null;
224+
}
225+
}

src/Command/ModelCommand.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
namespace Bake\Command;
1818

19+
use Bake\CodeGen\ColumnTypeExtractor;
1920
use Bake\CodeGen\FileBuilder;
2021
use Bake\Utility\Model\EnumParser;
2122
use Bake\Utility\TableScanner;
@@ -1210,11 +1211,24 @@ public function bakeTable(Table $model, array $data, Arguments $args, ConsoleIo
12101211
$filename = $path . 'Table' . DS . $name . 'Table.php';
12111212

12121213
$parsedFile = null;
1214+
$customColumnTypes = [];
12131215
if ($args->getOption('update')) {
12141216
$parsedFile = $this->parseFile($filename);
1217+
// Extract custom column types from existing file
1218+
if ($parsedFile && isset($parsedFile->class->methods['initialize'])) {
1219+
$customColumnTypes = $this->extractCustomColumnTypes($parsedFile->class->methods['initialize']);
1220+
}
12151221
}
12161222

12171223
$entity = $this->_entityName($model->getAlias());
1224+
$enums = $this->enums($model, $entity, $namespace);
1225+
1226+
// Merge custom column types with generated enums
1227+
// Remove custom types that are now handled by enums
1228+
foreach ($enums as $field => $enumClass) {
1229+
unset($customColumnTypes[$field]);
1230+
}
1231+
12181232
$data += [
12191233
'plugin' => $this->plugin,
12201234
'pluginPath' => $pluginPath,
@@ -1228,7 +1242,8 @@ public function bakeTable(Table $model, array $data, Arguments $args, ConsoleIo
12281242
'validation' => [],
12291243
'rulesChecker' => [],
12301244
'behaviors' => [],
1231-
'enums' => $this->enums($model, $entity, $namespace),
1245+
'enums' => $enums,
1246+
'customColumnTypes' => $customColumnTypes,
12321247
'connection' => $this->connection,
12331248
'fileBuilder' => new FileBuilder($io, "{$namespace}\Model\Table", $parsedFile),
12341249
];
@@ -1593,4 +1608,17 @@ protected function createAssociationAlias(array $association): string
15931608

15941609
return $this->_modelNameFromKey($foreignKey);
15951610
}
1611+
1612+
/**
1613+
* Extract custom column type mappings from existing initialize method
1614+
*
1615+
* @param string $initializeMethod The initialize method code
1616+
* @return array<string, string> Map of column names to type expressions
1617+
*/
1618+
protected function extractCustomColumnTypes(string $initializeMethod): array
1619+
{
1620+
$extractor = new ColumnTypeExtractor();
1621+
1622+
return $extractor->extract($initializeMethod);
1623+
}
15961624
}

src/Command/PluginCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int
7777
}
7878

7979
$pluginPath = $this->_pluginPath($plugin);
80-
if (is_dir($pluginPath)) {
80+
if (is_dir($pluginPath) && !$args->getOption('class-only')) {
8181
$io->out(sprintf('Plugin: %s already exists, no action taken', $plugin));
8282
$io->out(sprintf('Path: %s', $pluginPath));
8383

templates/bake/Model/table.twig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class {{ name }}Table extends Table{{ fileBuilder.classBuilder.implements ? ' im
6363
$this->getSchema()->setColumnType('{{ name }}', \Cake\Database\Type\EnumType::from(\{{ className }}::class));
6464
{%~ endfor %}
6565
{% endif %}
66+
{% if customColumnTypes is defined and customColumnTypes %}
67+
68+
{%~ for columnName, typeExpression in customColumnTypes %}
69+
$this->getSchema()->setColumnType('{{ columnName }}', {{ typeExpression|raw }});
70+
{%~ endfor %}
71+
{% endif %}
6672
{% if behaviors %}
6773

6874
{%~ for behavior, behaviorData in behaviors %}

0 commit comments

Comments
 (0)