Skip to content

Commit 3b7eedd

Browse files
committed
Add custom collection classes and update Blade demos for Laravel
1 parent 233483b commit 3b7eedd

10 files changed

Lines changed: 166 additions & 28 deletions

File tree

examples/laravel/app/Demo.php

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ public function customCollection(): void
117117
{
118118
// Builder chain → custom collection via #[CollectedBy]
119119
$reviews = Review::where('published', true)->get();
120-
$reviews->topRated(); // custom method from ReviewCollection
121-
$reviews->averageRating(); // custom method from ReviewCollection
120+
$top = $reviews->topRated(); // custom method from ReviewCollection
121+
$avg = $reviews->averageRating(); // custom method from ReviewCollection
122122
$reviews->first(); // inherited — returns Review|null
123123

124124
// Relationship properties also use the custom collection
@@ -133,7 +133,7 @@ public function eloquentClosure(): void
133133
{
134134
// Eloquent chunk — $orders inferred as Collection
135135
BlogAuthor::where('active', true)->chunk(100, function ($orders) {
136-
$orders->count(); // resolves to Eloquent Collection
136+
$total = $orders->count(); // resolves to Eloquent Collection
137137
});
138138

139139
// Explicit bare type hint inherits inferred generic args for foreach
@@ -162,8 +162,8 @@ public function eloquentClosure(): void
162162
*
163163
* Try:
164164
* 1. Ctrl+Click "app.name" to jump to config/app.php.
165-
* 2. Ctrl+Click "APP_KEY" to jump to .env.
166-
* 3. "Find All References" on "app.name" to see all usage sites.
165+
* 2. Ctrl+Click "app.key" to jump to config/app.php, then Ctrl+Click env('APP_KEY') to .env.
166+
* 3. "Find All References" on "app.name" to see all usage sites (including Blade views).
167167
*/
168168
public function laravelConfigEnv(): void
169169
{
@@ -174,9 +174,10 @@ public function laravelConfigEnv(): void
174174
Config::get('app.name');
175175
Config::set('app.env', 'production');
176176

177-
// Env helper
178-
env('APP_KEY');
179-
env('DB_PASSWORD', 'secret');
177+
// Config keys that use env() — Ctrl+Click jumps to the config file,
178+
// then Ctrl+Click the env() call there to jump to .env
179+
config('app.key'); // uses env('APP_KEY')
180+
config('database.connections.mysql.password'); // uses env('DB_PASSWORD')
180181
}
181182

182183

@@ -193,10 +194,11 @@ public function laravelConfigEnv(): void
193194
*/
194195
public function laravelNavigation(): void
195196
{
196-
// Blade Views
197-
view('welcome');
198-
View::make('admin.users.index');
199-
View::exists('emails.order_shipped');
197+
// Blade Views — passing typed data for in-template completion
198+
$posts = BlogPost::where('published', true)->get();
199+
view('welcome', compact('posts'));
200+
View::make('admin.users.index', ['users' => BlogAuthor::all()]);
201+
View::exists('emails.blog_published');
200202

201203
// Named Routes
202204
route('home');
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
/**
6+
* @template TKey of array-key
7+
* @template TModel of BlogAuthor
8+
* @extends \Illuminate\Database\Eloquent\Collection<TKey, TModel>
9+
*/
10+
class AuthorCollection extends \Illuminate\Database\Eloquent\Collection
11+
{
12+
/** @return static */
13+
public function active(): static { return $this->filter(fn($a) => $a->active); }
14+
15+
/** @return array<string> */
16+
public function emails(): array { return $this->pluck('email')->all(); }
17+
18+
/** @return static */
19+
public function byName(): static { return $this->sortBy('name'); }
20+
}

examples/laravel/app/Models/BlogAuthor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
namespace App\Models;
44

5+
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
56
use Illuminate\Database\Eloquent\Model;
67

8+
#[CollectedBy(AuthorCollection::class)]
9+
710
/**
811
* @method static \Illuminate\Database\Eloquent\Builder<static> withTrashed(bool $withTrashed = true)
912
* @method static \Illuminate\Database\Eloquent\Builder<static> onlyTrashed()

examples/laravel/app/Models/BlogPost.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace App\Models;
44

5+
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
56
use Illuminate\Database\Eloquent\Model;
67

8+
#[CollectedBy(PostCollection::class)]
79
class BlogPost extends Model
810
{
911
public function getTitle(): string { return ''; }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
/**
6+
* @template TKey of array-key
7+
* @template TModel of BlogPost
8+
* @extends \Illuminate\Database\Eloquent\Collection<TKey, TModel>
9+
*/
10+
class PostCollection extends \Illuminate\Database\Eloquent\Collection
11+
{
12+
/** @return static */
13+
public function published(): static { return $this->filter(fn($p) => $p->published); }
14+
15+
/** @return static */
16+
public function byNewest(): static { return $this->sortByDesc('created_at'); }
17+
18+
/** @return array<string> */
19+
public function titles(): array { return $this->pluck('title')->all(); }
20+
}

examples/laravel/config/app.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
'name' => env('APP_NAME', 'PHPantom'),
44
'env' => env('APP_ENV', 'local'),
55
'debug' => (bool) env('APP_DEBUG', true),
6+
'key' => env('APP_KEY'),
67
'timezone' => 'UTC',
78
'locale' => 'en',
89
];
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,37 @@
1-
<h1>Users</h1>
1+
{{-- PHPantom Laravel Demo: Admin Users Index --}}
2+
{{-- Demonstrates completion and navigation in nested Blade views --}}
3+
@php
4+
/**
5+
* @bladestan-signature
6+
* @var \App\Models\AuthorCollection $users
7+
*/
8+
@endphp
9+
10+
@extends('welcome')
11+
12+
@section('content')
13+
<h1>{{ __('messages.welcome') }} - Admin</h1>
14+
15+
<table>
16+
<thead>
17+
<tr>
18+
<th>Name</th>
19+
<th>Email</th>
20+
<th>Role</th>
21+
</tr>
22+
</thead>
23+
<tbody>
24+
@foreach($users->active()->byName() as $user)
25+
<tr>
26+
<td>{{ $user->name }}</td>
27+
<td>{{ $user->email }}</td>
28+
<td>{{ $user->role ?? 'N/A' }}</td>
29+
</tr>
30+
@endforeach
31+
</tbody>
32+
</table>
33+
34+
@if($users->isEmpty())
35+
<p>{{ trans('pagination.next') }}</p>
36+
@endif
37+
@endsection
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{{-- PHPantom Laravel Demo: Blog Post Published Email --}}
2+
{{-- Demonstrates variable completion inside included partials --}}
3+
@php
4+
/**
5+
* @bladestan-signature
6+
* @var \App\Models\BlogPost $post
7+
* @var \App\Models\BlogAuthor $author
8+
*/
9+
@endphp
10+
11+
<div style="font-family: sans-serif; padding: 20px;">
12+
<h2>{{ __('messages.welcome') }}!</h2>
13+
14+
<p>New post by {{ $author->name }}:</p>
15+
<h3>{{ $post->getTitle() }}</h3>
16+
<p>Slug: {{ $post->getSlug() }}</p>
17+
<p>Published: {{ $post->created_at->diffForHumans() }}</p>
18+
19+
<footer>
20+
<p>&copy; {{ config('app.name') }} {{ date('Y') }}</p>
21+
</footer>
22+
</div>

examples/laravel/resources/views/emails/order_shipped.blade.php

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
1+
{{-- PHPantom Laravel Demo: Blade Template --}}
2+
{{-- Try: Ctrl+Click on variables, config keys, route names, and translation keys --}}
3+
@php
4+
/**
5+
* @bladestan-signature
6+
* @var ?\App\Models\BlogAuthor $user
7+
* @var \App\Models\PostCollection $posts
8+
*/
9+
@endphp
10+
111
<!DOCTYPE html>
2-
<html>
3-
<head><title>PHPantom Laravel Demo</title></head>
4-
<body><h1>Welcome</h1></body>
12+
<html lang="{{ config('app.locale') }}">
13+
<head>
14+
<title>{{ config('app.name') }} - {{ __('messages.welcome') }}</title>
15+
</head>
16+
<body>
17+
<h1>{{ __('messages.welcome') }}</h1>
18+
19+
{{-- Variable completion: $user-> triggers member suggestions --}}
20+
@if($user)
21+
<p>Hello, {{ $user->name }}!</p>
22+
<p>Email: {{ $user->email }}</p>
23+
<p>Status: {{ $user->status->label() }}</p>
24+
@endif
25+
26+
{{-- Foreach with model completion — $posts->byNewest() uses PostCollection --}}
27+
@foreach($posts->byNewest() as $post)
28+
<article>
29+
<h2>{{ $post->title }}</h2>
30+
<p>By {{ $post->author->name }}</p>
31+
<span>{{ $post->created_at->diffForHumans() }}</span>
32+
</article>
33+
@endforeach
34+
35+
{{-- Route and translation helpers --}}
36+
<nav>
37+
<a href="{{ route('home') }}">{{ __('messages.welcome') }}</a>
38+
<a href="{{ route('admin.users.index') }}">{{ trans('auth.failed') }}</a>
39+
</nav>
40+
41+
{{-- Includes and nested views --}}
42+
@include('emails.order_shipped', ['order' => $order ?? null])
43+
44+
{{-- Conditional rendering with config --}}
45+
@if(config('app.debug'))
46+
<pre>Debug mode is on (env: {{ config('app.env') }})</pre>
47+
@endif
48+
</body>
549
</html>

0 commit comments

Comments
 (0)