### Testing Laravel Actions: Setup, Execute, Assert Pattern Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-action-pattern Demonstrates how to test Laravel Actions using a simple 'Setup, Execute, Assert' pattern. This approach avoids mocking and focuses on the action's input, execution, and output, making tests straightforward and reliable. ```PHP it('publishes a course with correct total duration', function (): void { // Setup $courseData = CourseDataFactory::factory() ->addLesson(title: 'Getting Started', videoCount: 3, duration: 15) ->addLesson(title: 'Advanced Topics', videoCount: 5, duration: 45) ->create(); $action = app(PublishCourseAction::class); // Execute $course = $action->execute($courseData); // Assert $this->assertDatabaseHas($course->getTable(), ['id' => $course->id]); expect($course->course_code)->not->toBeNull() ->and($course->total_duration)->toBe(60) ->and($course->lessons)->toHaveCount(2); }); ``` -------------------------------- ### Test Laravel Actions Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Illustrates the 'setup, execute, assert' pattern for testing service actions. It shows how to use factories for setup and verify database states and return values. ```php it('saves the course with correct total duration', function (): void { // Setup $courseData = CourseDataFactory::factory() ->addLessonDataFactory( LessonDataFactory::factory() ->withTitle('Getting Started') ->withVideoCount(3) ->withDuration(15) ) ->addLessonDataFactory( LessonDataFactory::factory() ->withTitle('Advanced Topics') ->withVideoCount(5) ->withDuration(45) ) ->create(); $action = app(PublishCourseAction::class); // Execute $course = $action->execute($courseData); // Assert $this->assertDatabaseHas($course->getTable(), [ 'id' => $course->id, ]); $expectedDuration = 15 + 45; expect($course->course_code)->not->toBeNull() ->and($course->total_duration)->toBe($expectedDuration) ->and($course->lessons)->toHaveCount(2); }); ``` -------------------------------- ### Application Layer Folder Structure Example Source: https://mayahi.net/books/thinking-in-domains-in-laravel/thinking-in-domains Demonstrates how different application layers (Dashboard, API, Console) can coexist under separate root namespaces, consuming the same domain logic. This separation enhances modularity and maintainability. ```php src/App/Dashboard/ # Instructor/admin dashboard (Blade/Inertia) ├── Controllers ├── Middlewares ├── Requests ├── Resources └── ViewModels src/App/Api/ # REST API ├── Controllers ├── Middlewares ├── Requests └── Resources src/App/Console/ # Artisan commands └── Commands ``` -------------------------------- ### Domain Folder Structure Example Source: https://mayahi.net/books/thinking-in-domains-in-laravel/thinking-in-domains Illustrates a potential folder structure for a domain, such as 'Courses', showcasing common subdirectories like Actions, Models, Events, and Rules. This structure allows for flexibility within each domain. ```php src/Domain/Courses/ ├── Actions ├── QueryBuilders ├── Collections ├── Data ├── Events ├── Exceptions ├── Listeners ├── Models ├── Rules └── States ``` ```php src/Domain/Students/ ├── Actions ├── Models ├── Events └── Rules ``` -------------------------------- ### PHP Type System Example Source: https://mayahi.net/books/thinking-in-domains-in-laravel/modeling-data-with-dtos Illustrates PHP's weak and dynamic typing, showing how a string can be automatically cast to a float and how type errors are discovered at runtime. ```php $price = '19.99'; // string function applyDiscount(float $price): float { // PHP automatically casts '19.99' to float return $price * 0.9; } applyDiscount($price); // Works, even though $price is a string ``` -------------------------------- ### Install spatie/laravel-model-states Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This command installs the spatie/laravel-model-states package using Composer. This package provides the necessary tools for managing states and transitions in your Laravel models. ```bash composer require spatie/laravel-model-states ``` -------------------------------- ### Testing Laravel Actions Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-action-pattern A PEST test example showing the standard setup, execute, and assert pattern for verifying action behavior. ```php it('publishes a course with correct total duration', function (): void { // Setup $courseData = CourseDataFactory::factory() ->addLesson(title: 'Getting Started', videoCount: 3, duration: 15) ->addLesson(title: 'Advanced Topics', videoCount: 5, duration: 45) ->create(); $action = app(PublishCourseAction::class); // Execute $course = $action->execute($courseData); // Assert $this->assertDatabaseHas($course->getTable(), ['id' => $course->id]); }); ``` -------------------------------- ### Implementing Polymorphic State Behavior Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Shows how to replace conditional logic with polymorphic state classes. This example demonstrates defining an abstract base class for course formats and specific implementations to handle enrollment logic. ```php abstract class CourseFormat { public function __construct(protected Course $course) {} abstract public function acceptsEnrollments(): bool; } class SelfPacedFormat extends CourseFormat { public function acceptsEnrollments(): bool { return true; } } class InstructorLedFormat extends CourseFormat { public function acceptsEnrollments(): bool { return $this->course->starts_at->isFuture(); } } ``` -------------------------------- ### Laravel Model State and Transition Testing Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Provides example tests for the model state transitions. It includes tests for successful transitions, preventing invalid transitions due to missing data (lessons), and preventing unconfigured state jumps. ```php it('transitions from draft to published', function (): void { $course = Course::factory() ->has(Lesson::factory()) ->create(); $course->state->transitionTo(PublishedCourseState::class); expect($course->state) ->toBeInstanceOf(PublishedCourseState::class); }); it('prevents publishing without lessons', function (): void { $course = Course::factory()->create(); $course->state->transitionTo(PublishedCourseState::class); })->throws(InvalidTransitionException::class); it('prevents invalid state transitions', function (): void { $course = Course::factory()->create(); // draft by default $course->state->transitionTo(ArchivedCourseState::class); })->throws(TransitionNotFound::class); ``` -------------------------------- ### Naive Conditional State Implementation Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums An example of the common but problematic approach of using if/else conditionals within an Eloquent model to handle state-dependent behavior. ```php class Course extends Model { public function getStateColor(): string { if ($this->state === CourseState::Draft) { return 'blue'; } if ($this->state === CourseState::Published) { return 'green'; } return 'grey'; } } ``` -------------------------------- ### Testing State Transitions Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This is a PHPUnit test example demonstrating how to test a state transition from 'draft' to 'published' for a `Course` model. It utilizes Laravel's factory and relationship features to set up the necessary preconditions for the test. ```php it('transitions from draft to published', function (): void { $course = Course::factory() ->has(Lesson::factory()) ->create(['state' => DraftCourseState::class]); $course->state->transitionTo(PublishedCourseState::class); expect($course->state)->toBeInstanceOf(PublishedCourseState::class); }); it('cannot transition from draft to published without lessons', function (): void { $course = Course::factory() ->create(['state' => DraftCourseState::class]); $this->expectException(InvalidTransitionException::class); $course->state->transitionTo(PublishedCourseState::class); }); ``` -------------------------------- ### Testing State-Specific Behavior Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Demonstrates how the State Pattern allows for isolated unit testing of specific state behaviors without needing complex model setups. ```php it('returns blue for draft state', function (): void { $course = Course::factory()->create(); $state = new DraftCourseState($course); expect($state->color())->toBe('blue'); }); ``` -------------------------------- ### Registering Custom Transitions with AllowTransition Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Shows how to register state transitions using the `AllowTransition` attribute on an abstract state class. This example registers a default state and custom transitions, including one that uses the `DraftToPublishedTransition` class. ```php #[ DefaultState(DraftCourseState::class), AllowTransition(DraftCourseState::class, PublishedCourseState::class, DraftToPublishedTransition::class), AllowTransition(PublishedCourseState::class, ArchivedCourseState::class), ] abstract class CourseState extends State ``` -------------------------------- ### Test Custom Query Builders Source: https://mayahi.net/books/thinking-in-domains-in-laravel/eloquent-without-the-bloat Example of how to write a unit test for a custom Eloquent query builder to ensure filtering logic works as expected. ```php it('filters only published courses', function (): void { $publishedCourse = Course::factory()->published()->create(); $draftCourse = Course::factory()->create(); expect(Course::query()->wherePublished()->whereKey($publishedCourse->id)->count())->toBe(1); expect(Course::query()->wherePublished()->whereKey($draftCourse->id)->count())->toBe(0); }); ``` -------------------------------- ### Thin Jobs Delegating Business Logic to Actions in PHP Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer This example illustrates how to create thin job classes in Laravel that delegate their core business logic to dedicated action classes. Jobs should manage queue infrastructure (retries, delays) and pass serialized data to actions for processing. This promotes a clean separation of concerns, making both jobs and actions more manageable and testable. For simpler cases, packages like spatie/laravel-queueable-action can dispatch actions directly as jobs. ```PHP namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use App\Domain\Enrollments\Actions\SendEnrollmentEmailAction; use App\Models\Enrollment; class SendEnrollmentConfirmationJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct(private Enrollment $enrollment) {} public function handle(SendEnrollmentEmailAction $sendEnrollmentEmailAction): void { $sendEnrollmentEmailAction->execute($this->enrollment); } } ``` ```PHP $sendEnrollmentEmailAction ->onQueue() ->execute($enrollment); ``` -------------------------------- ### Using PHP Enums for Simple Value Sets with Minimal Behavior Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This example showcases the use of native PHP 8.1 Enums for representing a set of related values with minimal associated behavior, such as a difficulty level for a course. It includes a `label()` method implemented using a `match` expression to provide a user-friendly name for each enum case. ```php enum DifficultyLevel: string { case Beginner = 'beginner'; case Intermediate = 'intermediate'; case Advanced = 'advanced'; public function label(): string { return match($this) { self::Beginner => 'Beginner', self::Intermediate => 'Intermediate', self::Advanced => 'Advanced', }; } } ``` -------------------------------- ### PHP Course Model with Naive State Conditionals Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Demonstrates a basic Course model in PHP using a naive approach with if-else statements to determine state-specific behavior like color. This method can lead to scattered and complex conditional logic as more states and behaviors are added. ```PHP class Course extends Model { public function getStateColor(): string { if ($this->state === CourseState::Draft) { return 'blue'; } if ($this->state === CourseState::Published) { return 'green'; } return 'grey'; } } ``` -------------------------------- ### Implementing a View Model for Data Presentation Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer Shows how to create a View Model class to encapsulate data preparation logic, keeping controllers thin. It provides methods for the view to access domain data and categories. ```php class CourseFormViewModel { public function __construct( private Instructor $instructor, private ?Course $course = null, ) {} public function course(): Course { return $this->course ?? new Course(); } public function categories(): Collection { return Category::visibleTo($this->instructor)->get(); } } ``` -------------------------------- ### Defining a Laravel Action Class Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-action-pattern An example of an Action class that encapsulates business logic. It uses constructor injection for dependencies and an 'execute' method for the primary operation. ```php class PublishCourseAction { public function __construct( private CreateLessonAction $createLessonAction, private GenerateCertificateAction $generateCertificateAction, ) {} public function execute(CourseData $courseData): Course { // Create course, process lessons, generate certificate template... } ``` -------------------------------- ### Testing State Transitions in Laravel Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Demonstrates how to test model state transitions using the spatie/laravel-model-states package. It includes verifying successful transitions and ensuring invalid transitions throw appropriate exceptions. ```php it('publishes a course', function (): void { $course = Course::factory()->create(); $course->state->transitionTo(PublishedCourseState::class); expect($course->state)->toBeInstanceOf(PublishedCourseState::class); }); it('prevents publishing without lessons', function (): void { $course = Course::factory()->create(); $course->state->transitionTo(PublishedCourseState::class); })->throws(InvalidTransitionException::class); ``` -------------------------------- ### Compose Complex Factory Scenarios Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Demonstrates how to chain states, relationships, and counts to create complex test data scenarios with minimal code. ```php $course = Course::factory() ->published() ->has( Enrollment::factory() ->count(3) ->for(Student::factory()->state(['full_name' => 'Jane Doe'])) ) ->create(); ``` -------------------------------- ### Organizing Application Modules by Feature Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer Demonstrates a modular directory structure for an application dashboard, grouping controllers, filters, and resources by business feature rather than file type. ```text App/Dashboard/Courses/ ├── Controllers/ │ ├── CoursesController.php │ ├── CourseStatusController.php │ ├── DraftCoursesController.php │ └── PublishCourseController.php ├── Filters/ │ ├── CourseCategoryFilter.php │ ├── CourseStatusFilter.php │ └── CourseDifficultyFilter.php ├── Middleware/ │ ├── EnsureValidCourseSettingsMiddleware.php │ └── EnsureInstructorOwnershipMiddleware.php ├── Queries/ │ ├── CourseIndexQuery.php │ └── LessonIndexQuery.php ├── Requests/ │ └── CourseRequest.php ├── Resources/ │ ├── CourseResource.php │ ├── LessonResource.php │ └── CourseDraftResource.php └── ViewModels/ ├── CourseIndexViewModel.php ├── CourseDraftViewModel.php └── CourseStatusViewModel.php ``` -------------------------------- ### Directory Structure for States and Transitions Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This outlines a recommended directory structure for organizing domain-specific states and transitions within a Laravel project. Placing these files within the `Domain/Courses/` directory alongside the `Models` keeps related logic cohesive and maintainable. ```text Domain/Courses/ ├── Models/ │ └── Course.php ├── States/ │ ├── CourseState.php │ ├── DraftCourseState.php │ ├── PublishedCourseState.php │ └── ArchivedCourseState.php └── Transitions/ └── DraftToPublishedTransition.php ``` -------------------------------- ### Invoking Action Dependencies Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-action-pattern Demonstrates why the 'execute' method is preferred over '__invoke' to avoid complex syntax when calling injected action dependencies. ```php // This does not work — PHP looks for a class method, not the invokable $this->createLessonAction($lessonData); // You need parentheses around it ($this->createLessonAction)($lessonData); ``` -------------------------------- ### Using View Models in Laravel Controllers Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer Demonstrates how to instantiate and pass a View Model to a view within a controller's create and edit methods. ```php class CoursesController { public function create() { $viewModel = new CourseFormViewModel(current_instructor()); return view('courses.form', $viewModel); } public function edit(Course $course) { $viewModel = new CourseFormViewModel(current_instructor(), $course); return view('courses.form', $viewModel); } } ``` -------------------------------- ### Transitioning Course State Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This PHP code shows how to trigger a state transition for a `Course` model using the `transitionTo` method. The package automatically handles routing the transition through configured classes or directly updating the state if no custom transition is defined. Attempting an invalid transition, like skipping a configured step, will result in a `TransitionNotFound` exception. ```php $course->state->transitionTo(PublishedCourseState::class); ``` -------------------------------- ### Testing Actions in Laravel Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Tests for Actions in Laravel, following a consistent setup, execute, and assert pattern. These tests verify the core logic of actions, including composing other actions and ensuring correct outcomes like database entries and calculated values. ```php it('saves the course with correct total duration', function (): void { // Setup $courseData = CourseDataFactory::factory() ->addLessonDataFactory( LessonDataFactory::factory() ->withTitle('Getting Started') ->withVideoCount(3) ->withDuration(15) ) ->addLessonDataFactory( LessonDataFactory::factory() ->withTitle('Advanced Topics') ->withVideoCount(5) ->withDuration(45) ) ->create(); $action = app(PublishCourseAction::class); // Execute $course = $action->execute($courseData); // Assert $this->assertDatabaseHas($course->getTable(), [ 'id' => $course->id, ]); $expectedDuration = 15 + 45; expect($course->course_code)->not->toBeNull() ->and($course->total_duration)->toBe($expectedDuration) ->and($course->lessons)->toHaveCount(2); }); ``` -------------------------------- ### Access State Behavior Directly on Laravel Model Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This example demonstrates how to access state-specific behavior directly from a Laravel model instance after setting up the `HasStates` trait and casting. The package automatically serializes and deserializes the state, allowing methods like `color()` and `canEnroll()` to be called on `$course->state`. ```PHP $course->state->color(); // 'blue' $course->state->canEnroll(); // false ``` -------------------------------- ### Build Course Index Query with Spatie QueryBuilder Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer Extracts complex filtering, sorting, and pagination logic for a course listing into a dedicated query class using the Spatie QueryBuilder package. This promotes reusability and keeps controllers thin. ```php namespace App\Dashboard\Courses\Queries; use Spatie\QueryBuilder\QueryBuilder; class CourseIndexQuery extends QueryBuilder { public function __construct(Request $request) { $query = Course::query() ->with([ 'instructor.profile', 'lessons.resources', ]); parent::__construct($query, $request); $this ->allowedFilters('title', 'instructor') ->allowedSorts('title'); } } ``` -------------------------------- ### Implement Factory States and Relationships Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Shows how to add state methods for conditional attributes and use afterCreating hooks to automatically generate related records. ```php public function published(): static { return $this->state(fn (array $attributes) => [ 'status' => PublishedCourseState::class, ])->afterCreating(function (Course $course) { Enrollment::factory()->for($course)->create(); }); } ``` -------------------------------- ### Manage Factory Relationships Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Illustrates how to compose factories to handle related models. It demonstrates using the 'has' method and the 'afterCreating' hook to automate the creation of dependent records. ```php $course = Course::factory() ->published() ->has(Enrollment::factory()->count(3)) ->create(); // Using afterCreating for automatic relationships public function published(): static { return $this->state(fn (array $attributes) => [ 'status' => PublishedCourseState::class, ])->afterCreating(function (Course $course) { Enrollment::factory()->for($course)->create(); }); } ``` -------------------------------- ### Custom Transition Class with Validation Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This PHP code defines a custom transition class, `DraftToPublishedTransition`, which extends Spatie's `Transition` base class. It includes custom logic within the `handle` method to validate that a course has at least one lesson before allowing the transition to the `PublishedCourseState`. It also handles saving the `published_at` timestamp and logging the history. ```php namespace Domain\Courses\Transitions; use Domain\Courses\Models\Course; use Domain\Courses\States\PublishedCourseState; use Spatie\ModelStates\Transition; use InvalidTransitionException; // Assuming this exception is defined elsewhere use History; // Assuming this class is defined elsewhere class DraftToPublishedTransition extends Transition { public function __construct( private Course $course, ) {} public function handle(): Course { if ($this->course->lessons()->count() === 0) { throw new InvalidTransitionException( 'A course must have at least one lesson before publishing.' ); } $this->course->state = new PublishedCourseState($this->course); $this->course->published_at = now(); $this->course->save(); History::log($this->course, 'Draft to Published'); return $this->course; } } ``` -------------------------------- ### PHP Type System Behavior Source: https://mayahi.net/books/thinking-in-domains-in-laravel/modeling-data-with-dtos Illustrates PHP's weak typing where variables can change types, demonstrating how dynamic typing can lead to runtime errors. ```php $price = '19.99'; function applyDiscount(float $price): float { return $price * 0.9; } applyDiscount($price); ``` -------------------------------- ### Using DTOs in Laravel Controllers Source: https://mayahi.net/books/thinking-in-domains-in-laravel/modeling-data-with-dtos Demonstrates how to instantiate a DTO from validated request data to gain autocompletion and static analysis benefits. ```php function store(StudentRequest $request, Student $student) { $data = new StudentData(...$request->validated()); $student->full_name = $data->full_name; $student->email = $data->email; $student->enrolled_at = $data->enrolled_at; } ``` -------------------------------- ### Create Course Form View Model in PHP Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer Encapsulates data needed for a course creation or edit form. It prepares instructor and course data, and fetches visible categories for the instructor. Implements Arrayable for direct view variable access and Responsable for JSON output. ```php class CourseFormViewModel { public function __construct( private Instructor $instructor, private ?Course $course = null, ) {} public function course(): Course { return $this->course ?? new Course(); } public function categories(): Collection { return Category::visibleTo($this->instructor)->get(); } } ``` -------------------------------- ### PHP: CreateLessonAction for Composing Actions Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-action-pattern Demonstrates a PHP Action class that uses constructor injection for a dependency (DurationCalculator) and an execute method to perform a specific business operation. It shows how to compose actions by injecting one action into another. ```php class CreateLessonAction { public function __construct(private DurationCalculator $durationCalculator) {} public function execute(LessonData $data): Lesson { $baseMinutes = $data->duration_minutes; if ($data->has_quiz) { $totalMinutes = $this->durationCalculator->withQuiz($baseMinutes, quizDuration: 10); } else { $totalMinutes = $this->durationCalculator->withoutQuiz($baseMinutes); } return new Lesson([ 'title' => $data->title, 'video_count' => $data->video_count, 'duration_minutes' => $totalMinutes, 'has_quiz' => $data->has_quiz, ]); } } ``` -------------------------------- ### Constructing DTOs via Factories or Static Methods Source: https://mayahi.net/books/thinking-in-domains-in-laravel/modeling-data-with-dtos Compares two approaches for mapping request data to DTOs: using a dedicated factory class for separation of concerns or a static constructor for convenience. ```php // Approach 1: Dedicated Factory class StudentDataFactory { public function fromRequest(StudentRequest $request): StudentData { return new StudentData( full_name: $request->get('full_name'), email: $request->get('email'), enrolled_at: Carbon::make($request->get('enrolled_at')), ); } } // Approach 2: Static Constructor class StudentData { public static function fromRequest(StudentRequest $request): self { return new self( full_name: $request->get('full_name'), email: $request->get('email'), enrolled_at: Carbon::make($request->get('enrolled_at')), ); } } ``` -------------------------------- ### Custom Transition Class for Course Publishing Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Defines a custom transition class `DraftToPublishedTransition` that extends the base `Transition` class. This class includes validation logic to ensure a course has at least one lesson before publishing, sets the published state, and logs the transition. ```php namespace Domain\Courses\Transitions; use Domain\Courses\Models\Course; use Domain\Courses\States\PublishedCourseState; use Spatie\ModelStates\Transition; class DraftToPublishedTransition extends Transition { public function __construct( private Course $course, ) {} public function handle(): Course { if ($this->course->lessons()->count() === 0) { throw new InvalidTransitionException( 'A course must have at least one lesson before publishing.' ); } $this->course->state = new PublishedCourseState($this->course); $this->course->published_at = now(); $this->course->save(); History::log($this->course, 'Draft to Published'); return $this->course; } } ``` -------------------------------- ### PHP Concrete State Classes for State Pattern Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums Provides concrete implementations of the abstract 'CourseState' class for 'Draft' and 'Published' states. Each class defines the specific behavior (color, enrollment eligibility) for its respective state, encapsulating state-specific logic within its own class. ```PHP class DraftCourseState extends CourseState { public function color(): string { return 'blue'; } public function canEnroll(): bool { return false; } } class PublishedCourseState extends CourseState { public function color(): string { return 'green'; } public function canEnroll(): bool { return $this->course->lessons()->count() > 0 && $this->course->format->acceptsEnrollments(); } } ``` -------------------------------- ### Event-Driven Models with Custom Event Classes and Subscribers in PHP Source: https://mayahi.net/books/thinking-in-domains-in-laravel/eloquent-without-the-bloat Demonstrates how to remap generic Eloquent model events (like 'saving', 'deleting') to specific, custom event classes. This approach allows for cleaner handling of model lifecycle events using dedicated subscriber classes, promoting dependency injection and keeping model classes concise. The subscriber can then perform complex logic, such as calculating total duration, by type-hinting against the specific event class. ```php namespace Domain\Courses\Models; use Illuminate\Database\Eloquent\Model; class Course extends Model { protected $dispatchesEvents = [ 'saving' => CourseSavingEvent::class, 'deleting' => CourseDeletingEvent::class, ]; } ``` ```php namespace Domain\Courses\Events; use Domain\Courses\Models\Course; class CourseSavingEvent { public function __construct(public Course $course) {} } ``` ```php namespace Domain\Courses\Subscribers; use Domain\Courses\Actions\CalculateTotalDurationAction; use Domain\Courses\Events\CourseSavingEvent; use Illuminate\Events\Dispatcher; class CourseSubscriber { public function __construct( private CalculateTotalDurationAction $calculateTotalDurationAction, ) {} public function saving(CourseSavingEvent $event): void { $course = $event->course; $course->total_duration = $this->calculateTotalDurationAction->execute($course); } public function subscribe(Dispatcher $dispatcher): void { $dispatcher->listen( CourseSavingEvent::class, self::class . '@saving' ); } } ``` ```php namespace App\Providers; use Illuminate\Support\Facades\Event; use Domain\Courses\Subscribers\CourseSubscriber; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Event::subscribe(CourseSubscriber::class); } } ``` -------------------------------- ### Dispatch Action Directly as Queued Job Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer Illustrates skipping the explicit job class for simple cases by using a package like spatie/laravel-queueable-action. This allows dispatching a domain action directly as a queued job, reducing boilerplate code. ```php $sendEnrollmentEmailAction ->onQueue() ->execute($enrollment); ``` -------------------------------- ### DTO with Static Constructor Approach Source: https://mayahi.net/books/thinking-in-domains-in-laravel/modeling-data-with-dtos Defines a StudentData DTO with a static `fromRequest` method for convenient creation from a StudentRequest, though it mixes application logic into the domain. ```php class StudentData { public function __construct( public readonly string $full_name, public readonly string $email, public readonly Carbon $enrolled_at, ) {} public static function fromRequest(StudentRequest $request): self { return new self( full_name: $request->get('full_name'), email: $request->get('email'), enrolled_at: Carbon::make($request->get('enrolled_at')), ); } } ``` -------------------------------- ### Implement Concrete Course States Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums These PHP classes define concrete states for a course: `DraftCourseState` and `PublishedCourseState`. Each class extends the abstract `CourseState` and implements the `color` and `canEnroll` methods to define the specific behavior and properties of each state. `PublishedCourseState` includes logic to check for lessons and enrollment acceptance. ```php class DraftCourseState extends CourseState { public function color(): string { return 'blue'; } public function canEnroll(): bool { return false; } } class PublishedCourseState extends CourseState { public function color(): string { return 'green'; } public function canEnroll(): bool { return $this->course()->lessons()->count() > 0 && $this->course()->format->acceptsEnrollments(); } } ``` -------------------------------- ### Test DTO Mapping and Validation Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Demonstrates testing static constructors for DTOs to ensure correct data mapping and validation. It covers both successful instantiation and exception handling for missing dependencies. ```php it('maps from store request', function (): void { $course = Course::factory()->create(); $dto = StudentData::fromStoreRequest(new StudentStoreRequest([ 'full_name' => 'Jane Doe', 'email' => 'jane@example.com', 'course_id' => $course->id, 'enrolled_at' => '2026-03-01', ])); expect($dto)->toBeInstanceOf(StudentData::class); }); it('throws when course does not exist', function (): void { StudentData::fromStoreRequest(new StudentStoreRequest([ 'full_name' => 'Jane Doe', 'email' => 'jane@example.com', 'enrolled_at' => '2026-03-01', ])); })->throws(ModelNotFoundException::class); ``` -------------------------------- ### Implement Factory States Source: https://mayahi.net/books/thinking-in-domains-in-laravel/testing-the-domain Shows how to add state methods to a factory to modify model attributes. These methods return a new factory instance, ensuring immutability during test execution. ```php public function published(): static { return $this->state(fn (array $attributes) => [ 'status' => PublishedCourseState::class, ]); } public function startsAt(string|Carbon $date): static { return $this->state(fn (array $attributes) => [ 'starts_at' => $date, ]); } ``` -------------------------------- ### Implement Concrete Course States in PHP Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums These PHP classes implement concrete course states (Draft and Published) by extending the abstract `CourseState`. Each class provides specific implementations for the `color` and `canEnroll` methods, defining the behavior and conditions associated with each state. ```PHP class DraftCourseState extends CourseState { public function color(): string { return 'blue'; } public function canEnroll(): bool { return false; } } class PublishedCourseState extends CourseState { public function color(): string { return 'green'; } public function canEnroll(): bool { return $this->course()->lessons()->count() > 0 && $this->course()->format->acceptsEnrollments(); } } ``` -------------------------------- ### Invoking Actions in Laravel Controllers Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-action-pattern Demonstrates how to resolve and execute actions within a controller context to perform business operations. ```php // In PublishCourseController $action = app(PublishCourseAction::class); $course = $action->execute($courseData); // In GenerateCertificateController $action = app(GenerateCertificateAction::class); $certificate = $action->execute($enrollment); ``` -------------------------------- ### Extracting HTTP Query Logic to Dedicated Classes in PHP Source: https://mayahi.net/books/thinking-in-domains-in-laravel/the-application-layer This snippet demonstrates how to extract complex HTTP query parameter handling, including filtering, sorting, and pagination, from controllers into dedicated query classes using the Spatie QueryBuilder package. This improves controller readability and reusability. The query class leverages dependency injection for the Request object and can be extended for specific controller needs. ```PHP class CoursesController { public function index() { $courses = QueryBuilder::for(Course::class) ->allowedFilters('title', 'instructor') ->allowedSorts('title') ->get(); } } ``` ```PHP namespace App\Dashboard\Courses\Queries; use Spatie\QueryBuilder\QueryBuilder; use Illuminate\Http\Request; use App\Models\Course; class CourseIndexQuery extends QueryBuilder { public function __construct(Request $request) { $query = Course::query() ->with([ 'instructor.profile', 'lessons.resources', ]); parent::__construct($query, $request); $this ->allowedFilters('title', 'instructor') ->allowedSorts('title'); } } ``` ```PHP class CoursesController { public function index(CourseIndexQuery $courseQuery) { $courses = $courseQuery->get(); } } ``` ```PHP class PublishedCoursesController { public function index(CourseIndexQuery $courseQuery) { $courses = $courseQuery ->wherePublished() ->get(); } } ``` -------------------------------- ### Composer Autoload Configuration for Domains Source: https://mayahi.net/books/thinking-in-domains-in-laravel/thinking-in-domains Shows how to configure `composer.json` to register new root namespaces for 'App', 'Domain', and 'Support' directories. This enables Laravel to correctly resolve classes within these custom namespaces. ```json { "autoload": { "psr-4": { "App\": "src/App/", "Domain\": "src/Domain/", "Support\": "src/Support/" } } } ``` -------------------------------- ### Testing Draft Course State Color in PHP Source: https://mayahi.net/books/thinking-in-domains-in-laravel/states-transitions-and-enums This snippet demonstrates how to independently test a specific state's behavior. It sets up a draft course state and asserts that its color method returns the expected value ('blue'). This highlights the testability benefit of the state pattern. ```PHP it('returns blue for draft state', function (): void { $course = Course::factory()->create(); $state = new DraftCourseState($course); expect($state->color())->toBe('blue'); }); ```