|
1 | 1 | # PHPantom — Laravel Support: Remaining Work |
2 | 2 |
|
3 | | -> Last updated: 2026-02-26 |
| 3 | +> Last updated: 2026-02-27 |
4 | 4 |
|
5 | 5 | This document tracks bugs, known gaps, and missing features in |
6 | 6 | PHPantom's Laravel Eloquent support. For the general architecture and |
@@ -32,4 +32,82 @@ virtual member provider design, see `ARCHITECTURE.md`. |
32 | 32 | are best-effort. |
33 | 33 | - **Facades fall back to `@method`.** Facades whose `getFacadeAccessor()` |
34 | 34 | returns a string alias cannot be resolved. `@method` tags on facade |
35 | | - classes provide completion without template intelligence. |
| 35 | + classes provide completion without template intelligence. |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Model property source gaps |
| 40 | + |
| 41 | +The `LaravelModelProvider` synthesizes virtual properties from several |
| 42 | +sources on Eloquent models. The table below summarises what we handle |
| 43 | +today and what is still missing. |
| 44 | + |
| 45 | +### What we cover |
| 46 | + |
| 47 | +| Source | Type info | Notes | |
| 48 | +|--------|-----------|-------| |
| 49 | +| `$casts` / `casts()` | Rich (built-in map, custom cast `get()` return type, enum, `Castable`, `CastsAttributes<TGet>` generics fallback) | | |
| 50 | +| `$attributes` defaults | Literal type inference (string, bool, int, float, null, array) | Fallback when no `$casts` entry | |
| 51 | +| `$fillable`, `$guarded`, `$hidden` | `mixed` | Last-resort column name fallback | |
| 52 | +| Legacy accessors (`getXAttribute()`) | Method's return type | | |
| 53 | +| Modern accessors (returns `Attribute`) | Always `mixed` | **See gap 1 below** | |
| 54 | +| Relationship methods | Generic params or body inference | | |
| 55 | + |
| 56 | +### Gaps (ranked by impact) |
| 57 | + |
| 58 | +#### 1. Modern accessor `Attribute<TGet>` generic extraction |
| 59 | + |
| 60 | +Modern accessors (Laravel 9+) return `Illuminate\Database\Eloquent\Casts\Attribute`. |
| 61 | +We detect these correctly and synthesize a virtual property, but the |
| 62 | +property is always typed `mixed`. When the return type carries a generic |
| 63 | +argument (e.g. `Attribute<string>` or `Attribute<string, never>` via |
| 64 | +`@return` or a native return type), we should extract the first generic |
| 65 | +parameter and use it as the property type. |
| 66 | + |
| 67 | +```php |
| 68 | +// @return Attribute<string> |
| 69 | +protected function firstName(): Attribute { ... } |
| 70 | +// Expected: $first_name typed as `string` |
| 71 | +// Actual: $first_name typed as `mixed` |
| 72 | +``` |
| 73 | + |
| 74 | +This is the most impactful gap because modern accessors are the |
| 75 | +recommended approach since Laravel 9. |
| 76 | + |
| 77 | +**Where to change:** `is_modern_accessor` already strips generics to |
| 78 | +match the base type. A companion function (or inline logic in `provide`) |
| 79 | +should extract the first generic arg from the return type string via |
| 80 | +`parse_generic_args` and pass it through instead of hard-coding `mixed`. |
| 81 | + |
| 82 | +#### 2. `$visible` array not included in column name extraction |
| 83 | + |
| 84 | +The `$visible` property lists attribute names that should appear in |
| 85 | +serialized output. It functions identically to `$fillable`/`$guarded`/ |
| 86 | +`$hidden` as a source of column names. |
| 87 | + |
| 88 | +**Where to change:** Add `"visible"` to the `targets` array in |
| 89 | +`extract_column_names` in `parser/classes.rs`. |
| 90 | + |
| 91 | +#### 3. `$dates` array (deprecated) |
| 92 | + |
| 93 | +Before `$casts`, Laravel used `protected $dates = [...]` to mark |
| 94 | +columns as Carbon instances. This was deprecated in favour of |
| 95 | +`casts()` with a `datetime` type, but older codebases still use it. |
| 96 | +Columns listed in `$dates` should be typed as `\Carbon\Carbon`. |
| 97 | + |
| 98 | +**Where to change:** Add a new `extract_dates_definitions` function in |
| 99 | +`parser/classes.rs` (similar to `extract_column_names` but returning |
| 100 | +`Vec<(String, String)>` with each column mapped to `\Carbon\Carbon`). |
| 101 | +Merge these into `casts_definitions` at a lower priority than explicit |
| 102 | +`$casts` entries, or add a separate field on `ClassInfo` and handle |
| 103 | +priority in the provider. |
| 104 | + |
| 105 | +#### 4. `$appends` array |
| 106 | + |
| 107 | +The `$appends` property lists accessor names that should always be |
| 108 | +included in `toArray()` / `toJson()`. These reference existing |
| 109 | +accessors, so in most cases the accessor method itself already produces |
| 110 | +the virtual property. Parsing `$appends` would only help when the |
| 111 | +accessor is defined in an unloaded parent class. |
| 112 | + |
| 113 | +**Priority:** Low. The accessor method is the real source of truth. |
0 commit comments