Expert-level PHP development with PHP 8+, Laravel, Composer, and modern best practices
Expert guidance for modern PHP development including PHP 8+ features, Laravel framework, Composer dependency management, and PHP best practices.
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
use RefreshDatabase;
public function test_can_list_posts(): void
{
Post::factory()->count(3)->create(['published' => true]);
Post::factory()->create(['published' => false]);
$response = $this->getJson('/api/posts');
$response->assertOk()
->assertJsonCount(3, 'data');
}
public function test_can_create_post_when_authenticated(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user, 'api')
->postJson('/api/posts', [
'title' => 'Test Post',
'content' => 'Test content with enough characters to pass validation.',
]);
$response->assertCreated()
->assertJsonPath('data.title', 'Test Post');
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
'user_id' => $user->id,
]);
}
public function test_cannot_create_post_when_not_authenticated(): void
{
$response = $this->postJson('/api/posts', [
'title' => 'Test Post',
'content' => 'Test content',
]);
$response->assertUnauthorized();
}
public function test_validates_post_creation(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user, 'api')
->postJson('/api/posts', [
'title' => '', // Invalid
'content' => 'Short', // Too short
]);
$response->assertUnprocessable()
->assertJsonValidationErrors(['title', 'content']);
}
public function test_can_update_own_post(): void
{
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);
$response = $this->actingAs($user, 'api')
->putJson("/api/posts/{$post->id}", [
'title' => 'Updated Title',
'content' => 'Updated content with enough characters.',
]);
$response->assertOk();
$this->assertDatabaseHas('posts', [
'id' => $post->id,
'title' => 'Updated Title',
]);
}
public function test_cannot_update_other_user_post(): void
{
$user = User::factory()->create();
$otherUser = User::factory()->create();
$post = Post::factory()->create(['user_id' => $otherUser->id]);
$response = $this->actingAs($user, 'api')
->putJson("/api/posts/{$post->id}", [
'title' => 'Updated Title',
]);
$response->assertForbidden();
}
}
<?php
namespace Tests\Unit;
use App\Models\User;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
public function test_user_has_posts(): void
{
$user = User::factory()->create();
$posts = Post::factory()->count(3)->create(['user_id' => $user->id]);
$this->assertCount(3, $user->posts);
$this->assertTrue($user->posts->contains($posts->first()));
}
public function test_is_admin_returns_true_for_admin_users(): void
{
$admin = User::factory()->create(['is_admin' => true]);
$user = User::factory()->create(['is_admin' => false]);
$this->assertTrue($admin->isAdmin());
$this->assertFalse($user->isAdmin());
}
}
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->sentence(),
'slug' => fake()->slug(),
'content' => fake()->paragraphs(5, true),
'excerpt' => fake()->paragraph(),
'published' => false,
'published_at' => null,
'tags' => fake()->words(3),
];
}
public function published(): static
{
return $this->state(fn (array $attributes) => [
'published' => true,
'published_at' => now(),
]);
}
public function withUser(User $user): static
{
return $this->state(fn (array $attributes) => [
'user_id' => $user->id,
]);
}
}
<?php
declare(strict_types=1);
// Always use strict types
// Use type declarations for parameters and return types
// Use property types where possible
<?php
// Use constructor injection
class UserService
{
public function __construct(
private UserRepository $repository,
private EventDispatcher $dispatcher,
) {}
public function createUser(array $data): User
{
$user = $this->repository->create($data);
$this->dispatcher->dispatch(new UserCreated($user));
return $user;
}
}
ā Not using strict types: Always declare(strict_types=1) ā Fat controllers: Extract logic to services ā N+1 queries: Use eager loading ā No type declarations: Use types everywhere ā Ignoring PSR standards: Follow PSR-4, PSR-12 ā Direct DB queries in controllers: Use repositories ā Missing validation: Always validate input ā No tests: Write tests for critical code
Detailed material lives alongside this skill and is read on demand: