Lines
100.00%
27 / 27
Functions and Methods
100.00%
1 / 1
Classes and Traits
100.00%
1 / 1
| Name | Lines | Functions and Methods | CRAP | Classes and Traits | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| ReferenceResolver | 100.00% | 27 / 27 | 100.00% | 1 / 1 | 7 | 100.00% | 1 / 1 | |||
| resolve | 100.00% | 27 / 27 | 100.00% | 1 / 1 | 7 | |||||
| 1 | <?php | |
| 2 | ||
| 3 | declare(strict_types=1); | |
| 4 | ||
| 5 | namespace BlueprintAU\Radiant\Attributes; | |
| 6 | ||
| 7 | use BlueprintAU\Radiant\Metadata\MetadataFactory; | |
| 8 | ||
| 9 | /** | |
| 10 | * Resolves a foreign-key reference to its table name. | |
| 11 | * | |
| 12 | * A reference is EITHER a plain table name (`roles`) OR a model | |
| 13 | * class-string (`App\Models\Role`) whose table resolves through the | |
| 14 | * {@see MetadataFactory} — the same convention the model itself uses, so a | |
| 15 | * renamed table never breaks the FK silently. Shared by | |
| 16 | * {@see ForeignKey::resolvedReferences()} (class-level composite | |
| 17 | * constraints) and the `Blueprint` schema layer (single-column `foreign:` | |
| 18 | * column flags) — one resolution rule, one place. | |
| 19 | * | |
| 20 | * The two forms are disambiguated by the presence of a `\`: no valid SQL | |
| 21 | * table name contains a backslash, and every namespaced class-string does. | |
| 22 | * A backslash-free input that names an EXISTING class is rejected — PHP's | |
| 23 | * `::class` constant never emits a leading backslash, so a global-namespace | |
| 24 | * class-string (`DateTimeImmutable::class`) would otherwise slip through | |
| 25 | * the gate and masquerade as a table name. | |
| 26 | * | |
| 27 | * @phpstan-type ForeignKeyReference class-string<\BlueprintAU\Radiant\Model>|string | |
| 28 | */ | |
| 29 | final class ReferenceResolver | |
| 30 | { | |
| 31 | /** | |
| 32 | * Resolve a reference to its table name. | |
| 33 | * | |
| 34 | * @param ForeignKeyReference $reference | |
| 35 | * @return string | |
| 36 | * @throws \InvalidArgumentException | |
| 37 | */ | |
| 38 | public static function resolve(string $reference): string | |
| 39 | { | |
| 40 | if (!str_contains($reference, '\\')) { | |
| 41 | // A backslash-free input is a table name — unless it names an | |
| 42 | // existing class. PHP's `::class` constant has NO leading | |
| 43 | // backslash, so a global-namespace class-string would land here | |
| 44 | // and masquerade as a table (the FK would compile against a | |
| 45 | // nonexistent table and fail downstream with a confusing SQL | |
| 46 | // error). Reject it here, where the message can say why. | |
| 47 | if (class_exists($reference) && !is_a($reference, \BlueprintAU\Radiant\Model::class, true)) { | |
| 48 | throw new \InvalidArgumentException( | |
| 49 | "A foreign key references [{$reference}], which is a class but not a " | |
| 50 | . 'Radiant model; a foreign key must reference a table name or a ' | |
| 51 | . 'model class-string.' | |
| 52 | ); | |
| 53 | } | |
| 54 | ||
| 55 | return $reference; | |
| 56 | } | |
| 57 | ||
| 58 | if (!class_exists($reference)) { | |
| 59 | throw new \InvalidArgumentException( | |
| 60 | "A foreign key references [{$reference}], which is neither a table name " | |
| 61 | . 'nor an existing model class-string.' | |
| 62 | ); | |
| 63 | } | |
| 64 | ||
| 65 | if (!is_a($reference, \BlueprintAU\Radiant\Model::class, true)) { | |
| 66 | throw new \InvalidArgumentException( | |
| 67 | "A foreign key references [{$reference}], which is a class but not a " | |
| 68 | . 'Radiant model; a foreign key must reference a table name or a ' | |
| 69 | . 'model class-string.' | |
| 70 | ); | |
| 71 | } | |
| 72 | ||
| 73 | $table = MetadataFactory::for($reference)->tableName; | |
| 74 | ||
| 75 | if ($table === null) { | |
| 76 | throw new \InvalidArgumentException( | |
| 77 | "A foreign key references model [{$reference}], which owns no table " | |
| 78 | . '(no columns); a foreign key must reference a table-owning model or a ' | |
| 79 | . 'plain table name.' | |
| 80 | ); | |
| 81 | } | |
| 82 | ||
| 83 | return $table; | |
| 84 | } | |
| 85 | } |