Skip to content

Commit 53dbeac

Browse files
committed
inverse-relations and bugs-fixing
1 parent cb45e67 commit 53dbeac

3 files changed

Lines changed: 86 additions & 14 deletions

File tree

src/generator/default/dbmodel.php

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,52 @@
44
* @var string $namespace
55
* @var string $relationNamespace
66
**/
7+
use cebe\yii2openapi\lib\items\AttributeRelation;
78
use yii\helpers\Inflector;
89
use yii\helpers\VarDumper;
910

11+
$allCoveredClasses = array_merge(
12+
array_map(fn($r) => $r->getClassName(), (array)$model->relations),
13+
array_map(fn($r) => $r->relatedClassName, (array)$model->many2many),
14+
array_map(fn($r) => $r->getClassName(), (array)$model->nonDbRelations)
15+
);
16+
/**
17+
* Resolve inverse relation method names from the FK column name of the other model.
18+
* Relations already covered by $model->relations, many2many, or nonDbRelations are skipped.
19+
* Relations from models declared with "x-table: false" are skipped (no real table, no FK).
20+
*
21+
* Naming logic (model being generated = e.g. "Lead", $modelSnake = "lead"):
22+
* 1. Take the FK column name from the other model (e.g. Order.customer_lead_id)
23+
* 2. Strip trailing "_id" → "customer_lead"
24+
* 3. Strip trailing "_<modelSnake>" → "customer"
25+
* 4. If a prefix remains, prepend it to the pluralized class name:
26+
* "customer" + "Orders" → getCustomerOrders()
27+
* If no prefix remains (plain FK like "lead_id"):
28+
* "" + "Orders" → getOrders()
29+
*
30+
* Examples: Order has three FK columns pointing to Lead ($model->name = "Lead"):
31+
*
32+
* FK column (on Order) | generated on Order | generated on Lead (inverse)
33+
* --------------------------|-------------------------|----------------------------
34+
* lead_id | getLead() | getOrders()
35+
* customer_lead_id | getCustomerLead() | getCustomerOrders()
36+
* billing_lead_id | getBillingLead() | getBillingOrders()
37+
*/
38+
$modelSnake = Inflector::underscore($model->name);
39+
$inverseRelations = array_map(function (AttributeRelation $relation) use ($modelSnake): array {
40+
$inverseMethod = $relation->getMethod() === 'hasOne' ? 'hasMany' : 'hasOne';
41+
$classBase = $inverseMethod === 'hasMany' ? Inflector::pluralize($relation->getCamelName()) : $relation->getCamelName();
42+
$fkBase = preg_replace('/_id$/', '', $relation->getForeignName());
43+
$prefix = preg_replace('/_?' . preg_quote($modelSnake, '/') . '$/', '', $fkBase);
44+
return [
45+
'relation' => $relation,
46+
'inverseMethod' => $inverseMethod,
47+
'inverseName' => $prefix !== '' ? Inflector::camelize($prefix) . $classBase : $classBase,
48+
];
49+
}, array_filter(
50+
(array)$model->belongsToRelations,
51+
fn(AttributeRelation $r) => !in_array($r->getClassName(), $allCoveredClasses) && $r->getTableName() !== ''
52+
));
1053
?>
1154
<?= '<?php' ?>
1255

@@ -45,6 +88,15 @@
4588
<?php foreach ($model->many2many as $relation): ?>
4689
* @property array|\<?= trim($relationNamespace, '\\') ?>\<?= $relation->relatedClassName ?>[] $<?= Inflector::variablize($relation->name) ?>
4790

91+
<?php endforeach; ?>
92+
<?php foreach ($inverseRelations as $inverse): ?>
93+
<?php if ($inverse['inverseMethod'] === 'hasOne'):?>
94+
* @property \<?= trim($relationNamespace, '\\') ?>\<?= $inverse['relation']->getClassName() ?> $<?= Inflector::variablize($inverse['inverseName']) ?>
95+
96+
<?php else:?>
97+
* @property array|\<?= trim($relationNamespace, '\\') ?>\<?= $inverse['relation']->getClassName() ?>[] $<?= Inflector::variablize($inverse['inverseName']) ?>
98+
99+
<?php endif?>
48100
<?php endforeach; ?>
49101
*/
50102
abstract class <?= $model->getClassName() ?> extends \yii\db\ActiveRecord
@@ -146,14 +198,17 @@ public function get<?= $relation->getCamelName() ?>()
146198
<?php endif;?>
147199
}
148200
<?php endforeach; ?>
149-
<?php $i = 1; $usedRelationNames = [];
150-
foreach ($model->belongsToRelations as $relationName => $relation): ?><?php $number = in_array($relation->getCamelName(), $usedRelationNames) ? $i : '' ?>
201+
<?php foreach ($model->nonDbRelations as $nonDbRelation): ?>
151202

152-
# belongs to relation
153-
public function get<?= $relation->getCamelName() . ($number) ?>()
203+
abstract public function get<?= $nonDbRelation->getCamelName() ?>(): \yii\db\ActiveQuery;
204+
<?php endforeach; ?>
205+
<?php foreach ($inverseRelations as $inverse): ?>
206+
207+
# inverse relation
208+
public function get<?= $inverse['inverseName'] ?>()
154209
{
155-
return $this-><?= $relation->getMethod() ?>(\<?= trim($relationNamespace, '\\') ?>\<?= $relation->getClassName() ?>::class, <?php
156-
echo $relation->linkToString() ?>);
210+
return $this-><?= $inverse['inverseMethod'] ?>(\<?= trim($relationNamespace, '\\') ?>\<?= $inverse['relation']->getClassName() ?>::class, <?php
211+
echo $inverse['relation']->linkToString() ?>);
157212
}
158-
<?php $i++; $usedRelationNames[] = $relation->getCamelName(); endforeach; ?>
213+
<?php endforeach; ?>
159214
}

src/lib/FakerStubResolver.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st
284284
$items = $property->items;
285285

286286
if (!$items) {
287-
return $this->arbitraryArray();
287+
return '[]';
288288
}
289289

290290
if ($items instanceof Reference) {
@@ -306,6 +306,9 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st
306306

307307
if ($type === 'object') {
308308
$result = $this->fakeForObject($items);
309+
if ($result === '[]') {
310+
return '[]';
311+
}
309312
return $this->wrapInArray($result, $uniqueItems, $count);
310313
}
311314

@@ -315,26 +318,28 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st
315318
/**
316319
* @internal
317320
*/
318-
public function fakeForObject(SpecObjectInterface $items): string
321+
public function fakeForObject(SpecObjectInterface $items, int $depth = 1): string
319322
{
320323
if (!$items->properties) {
321-
return $this->arbitraryArray();
324+
return '[]';
322325
}
323326

324-
$props = '[' . PHP_EOL;
327+
$indent = str_repeat(' ', $depth + 3);
328+
$closingIndent = str_repeat(' ', $depth + 2);
329+
$parts = [];
325330

326331
foreach ($items->properties as $name => $prop) {
327332
/** @var SpecObjectInterface $prop */
328333

329334
if (!empty($prop->properties)) { // nested object
330-
$result = $this->{__FUNCTION__}($prop);
335+
$result = $this->fakeForObject($prop, $depth + 1);
331336
} else {
332337
$result = $this->aElementFaker(['items' => $prop->getSerializableData()], $name);
333338
}
334-
$props .= '\'' . $name . '\' => ' . $result . ',' . PHP_EOL;
339+
$parts[] = $indent . '\'' . $name . '\' => ' . $result . ',';
335340
}
336341

337-
$props .= ']';
342+
$props = '[' . PHP_EOL . implode(PHP_EOL, $parts) . PHP_EOL . $closingIndent . ']';
338343

339344
return $props;
340345
}

src/lib/openapi/PropertySchema.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public function __construct(SpecObjectInterface $property, string $name, Compone
100100
$this->isPk = $name === $schema->getPkName();
101101

102102
$onUpdate = $onDelete = $xFaker = $reference = $fkColName = null;
103+
$xDbTypeFalse = false;
103104

104105
foreach ($property->allOf ?? [] as $element) {
105106
// x-fk-on-delete | x-fk-on-update
@@ -122,6 +123,11 @@ public function __construct(SpecObjectInterface $property, string $name, Compone
122123
if (!empty($element->{CustomSpecAttr::FK_COLUMN_NAME})) {
123124
$fkColName = $element->{CustomSpecAttr::FK_COLUMN_NAME};
124125
}
126+
127+
// x-db-type: false → treat as non-DB reference (no FK column, no migration)
128+
if (isset($element->{CustomSpecAttr::DB_TYPE}) && $element->{CustomSpecAttr::DB_TYPE} === false) {
129+
$xDbTypeFalse = true;
130+
}
125131
}
126132

127133
if (
@@ -143,6 +149,9 @@ public function __construct(SpecObjectInterface $property, string $name, Compone
143149
$this->xFaker = $xFaker;
144150
$this->property = $reference;
145151
$property = $this->property;
152+
} elseif ($xDbTypeFalse && $reference instanceof Reference) {
153+
$this->property = $reference;
154+
$property = $this->property;
146155
}
147156

148157
// don't go reference part if `x-no-relation` is true
@@ -152,6 +161,9 @@ public function __construct(SpecObjectInterface $property, string $name, Compone
152161

153162
if ($property instanceof Reference) {
154163
$this->initReference();
164+
if ($xDbTypeFalse) {
165+
$this->isNonDbReference = true;
166+
}
155167
} elseif (
156168
isset($property->type, $property->items) && $property->type === 'array'
157169
&& $property->items instanceof Reference

0 commit comments

Comments
 (0)