|
12 | 12 | * 3. SCAFFOLDING — supporting definitions (scroll past these) |
13 | 13 | */ |
14 | 14 |
|
15 | | -namespace Demo; |
| 15 | +namespace Demo { |
16 | 16 |
|
17 | 17 | use Exception; |
18 | 18 | use Stringable; |
@@ -821,6 +821,12 @@ public function switchInsideCase(string $driver): void |
821 | 821 | // Methods starting with "scope" (e.g. scopeActive) produce virtual methods with |
822 | 822 | // the prefix stripped and first letter lowercased (e.g. active). The $query |
823 | 823 | // parameter is removed. Scopes are available as both static and instance methods. |
| 824 | +// |
| 825 | +// Eloquent Builder methods are forwarded as static methods on model classes. |
| 826 | +// User::where('active', true)->orderBy('name')->get() resolves end-to-end. |
| 827 | +// Return types are mapped: Builder chain methods return Builder<ConcreteModel>, |
| 828 | +// and TModel parameters resolve to the concrete model class. Methods from |
| 829 | +// Query\Builder (via @mixin) are included as well. |
824 | 830 |
|
825 | 831 | class BlogAuthor extends \Illuminate\Database\Eloquent\Model |
826 | 832 | { |
@@ -869,6 +875,14 @@ public function demo(): void |
869 | 875 | // Scope methods — static access |
870 | 876 | BlogAuthor::active(); // also available as static |
871 | 877 | BlogAuthor::ofGenre('fiction'); // $genre parameter preserved, $query stripped |
| 878 | + |
| 879 | + // Builder-as-static forwarding |
| 880 | + BlogAuthor::where('active', true); // returns Builder<BlogAuthor> |
| 881 | + BlogAuthor::where('active', 1)->get(); // returns Collection<BlogAuthor> |
| 882 | + BlogAuthor::where('active', 1)->first(); // returns BlogAuthor|null |
| 883 | + BlogAuthor::orderBy('name')->limit(10)->get(); // full chain resolution |
| 884 | + // Query\Builder methods (@mixin) are also forwarded: |
| 885 | + BlogAuthor::whereIn('id', [1, 2])->groupBy('genre')->get(); |
872 | 886 | } |
873 | 887 | } |
874 | 888 |
|
@@ -2584,4 +2598,101 @@ class BlogTag extends \Illuminate\Database\Eloquent\Model |
2584 | 2598 | public function getLabel(): string { return ''; } |
2585 | 2599 | } |
2586 | 2600 |
|
| 2601 | +} // end namespace Demo |
| 2602 | + |
| 2603 | +// ── Illuminate Stubs ──────────────────────────────────────────────────────── |
| 2604 | +// Minimal stubs matching real Laravel classes so that the Eloquent demo models |
| 2605 | +// above resolve Builder methods, relationship properties, and scope forwarding |
| 2606 | +// without requiring a real Laravel installation. |
| 2607 | + |
| 2608 | +namespace Illuminate\Database\Eloquent { |
| 2609 | + |
| 2610 | + abstract class Model { |
| 2611 | + /** @return \Illuminate\Database\Eloquent\Builder<static> */ |
| 2612 | + public static function query() {} |
| 2613 | + } |
| 2614 | + |
| 2615 | + /** |
| 2616 | + * @template TModel of \Illuminate\Database\Eloquent\Model |
| 2617 | + * |
| 2618 | + * @mixin \Illuminate\Database\Query\Builder |
| 2619 | + */ |
| 2620 | + class Builder implements \Illuminate\Contracts\Database\Eloquent\Builder { |
| 2621 | + /** @use \Illuminate\Database\Concerns\BuildsQueries<TModel> */ |
| 2622 | + use \Illuminate\Database\Concerns\BuildsQueries; |
2587 | 2623 |
|
| 2624 | + /** |
| 2625 | + * @param (\Closure(static): mixed)|string|array $column |
| 2626 | + * @return $this |
| 2627 | + */ |
| 2628 | + public function where($column, $operator = null, $value = null, $boolean = 'and') {} |
| 2629 | + |
| 2630 | + /** @return \Illuminate\Database\Eloquent\Collection<int, TModel> */ |
| 2631 | + public function get($columns = ['*']) { return new Collection(); } |
| 2632 | + } |
| 2633 | + |
| 2634 | + /** |
| 2635 | + * @template TKey of array-key |
| 2636 | + * @template TModel of \Illuminate\Database\Eloquent\Model |
| 2637 | + */ |
| 2638 | + class Collection { |
| 2639 | + /** @return TModel|null */ |
| 2640 | + public function first(): mixed { return null; } |
| 2641 | + public function count(): int { return 0; } |
| 2642 | + } |
| 2643 | +} |
| 2644 | + |
| 2645 | +namespace Illuminate\Database\Eloquent\Relations { |
| 2646 | + class HasMany {} |
| 2647 | + class HasOne {} |
| 2648 | + class BelongsTo {} |
| 2649 | + class BelongsToMany {} |
| 2650 | + class MorphOne {} |
| 2651 | + class MorphMany {} |
| 2652 | + class MorphTo {} |
| 2653 | + class MorphToMany {} |
| 2654 | + class HasManyThrough {} |
| 2655 | +} |
| 2656 | + |
| 2657 | +namespace Illuminate\Database\Concerns { |
| 2658 | + |
| 2659 | + /** |
| 2660 | + * @template TValue |
| 2661 | + */ |
| 2662 | + trait BuildsQueries { |
| 2663 | + /** @return TValue|null */ |
| 2664 | + public function first($columns = ['*']) { return null; } |
| 2665 | + } |
| 2666 | +} |
| 2667 | + |
| 2668 | +namespace Illuminate\Database\Query { |
| 2669 | + |
| 2670 | + class Builder { |
| 2671 | + /** |
| 2672 | + * @param string $column |
| 2673 | + * @return $this |
| 2674 | + */ |
| 2675 | + public function whereIn($column, $values, $boolean = 'and', $not = false) { return $this; } |
| 2676 | + |
| 2677 | + /** @return $this */ |
| 2678 | + public function groupBy(...$groups) { return $this; } |
| 2679 | + |
| 2680 | + /** @return $this */ |
| 2681 | + public function orderBy($column, $direction = 'asc') { return $this; } |
| 2682 | + |
| 2683 | + /** @return $this */ |
| 2684 | + public function limit($value) { return $this; } |
| 2685 | + |
| 2686 | + /** |
| 2687 | + * @return \Illuminate\Support\Collection<int, \stdClass> |
| 2688 | + */ |
| 2689 | + public function get($columns = ['*']) {} |
| 2690 | + } |
| 2691 | +} |
| 2692 | + |
| 2693 | +namespace Illuminate\Contracts\Database\Eloquent { |
| 2694 | + /** |
| 2695 | + * @mixin \Illuminate\Database\Eloquent\Builder |
| 2696 | + */ |
| 2697 | + interface Builder {} |
| 2698 | +} |
0 commit comments