Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Notes on stitcher.io's Laravel beyond CRUD

Laravel beyond CRUD

stitcher.io

A blog series for PHP developers working on larger-than-average Laravel projects

Written for projects with a development lifespan of six to twelve months, with a team of three to six developers working on them simultaneously.

Chapter 1: Domain oriented Laravel

https://stitcher.io/blog/laravel-beyond-crud-01-domain-oriented-laravel

"Domain" comes from DDD. It describes a set of the business problems to be solved.

There often isn't a one-to-one mapping between controllers and models.

The domain is all the business logic.

Everything else (ish...) is code that uses/consumes that domain to integrate it with the framework and exposes it to the end-user.

The domain will hold classes like models, query builders, domain events, validation rules and more.

app/Domain/Invoices/
    |── Actions
    ├── QueryBuilders
    ├── Collections
    ├── DataTransferObjects
    ├── Events
    ├── Exceptions
    ├── Listeners
    ├── Models
    ├── Rules
    └── States

app/Domain/Customers/
    // etc

Application layer:

// The admin HTTP application
app/App/Admin/
    ├── Controllers
    ├── Middlewares
    ├── Requests
    ├── Resources
    └── ViewModels

// The REST API application
app/App/Api/
    ├── Controllers
    ├── Middlewares
    ├── Requests
    └── Resources

// The console application
app/App/Console/
    └── Commands

It's easier to change the namespacing to App/, Domain/ and Support/ (Support is for everything that doesn't strictly belong anywhere else).

This can be done in composer.json, and bootstrap/app.php.

Start thinking in groups of related business concepts, rather than in groups of code with the same technical properties.

Chapter 2: Working With Data

https://stitcher.io/blog/laravel-beyond-crud-02-working-with-data

Structuring unstructured data

Use DTOs to pass data back and forth. THe problem is mapping the data to and from DTOs.

With PHP7.4, typed properties can be used. Thus a strongly-typed application.

A dedicated factory is an option, but it adds application-specific logic in the Domain layer.

Spatie have a package for this: https://github.com/spatie/data-transfer-object

Chapter 3: Actions

https://stitcher.io/blog/laravel-beyond-crud-03-actions

Chapter 4 Models

Don't store business logic in models. These small methods add up.

Model files can be too long, made up of many small business-related methods.

Move responsibilities to other classes.

Instead of keeping scopes in a Model class, use a custom Query Builder class.

This class can be loaded in to the model, overriding the default Query Builder.

namespace Domain\Invoices\QueryBuilders;

use Domain\Invoices\States\Paid;
use Illuminate\Database\Eloquent\Builder;

class InvoiceQueryBuilder extends Builder
{
    public function wherePaid(): self
    {
        return $this->whereState('status', Paid::class);
    }
}

// in the model

class Invoice extends Model 
{
    public function newEloquentBuilder($query): InvoiceQueryBuilder
    {
        return new InvoiceQueryBuilder($query);
    }
}

The same can be done with Model Collections, by using the newCollection() method.

namespace Domain\Invoices\Collections;

use Domain\Invoices\Models\InvoiceLines;
use Illuminate\Database\Eloquent\Collection;

class InvoiceLineCollection extends Collection
{
    public function creditLines(): self
    {
        return $this->filter(function (InvoiceLine $invoiceLine) {
            return $invoiceLine->isCreditLine();
        });
    }
}

// then in the model

class InvoiceLine extends Model 
{
    public function newCollection(array $models = []): InvoiceLineCollection
    {
        return new InvoiceLineCollection($models);
    }

    public function isCreditLine(): bool
    {
        return $this->price < 0.0;
    }
}

Chapter 5: Models with the State Pattern

https://stitcher.io/blog/laravel-beyond-crud-05-states

The state pattern is one of the best ways to add state-specific behaviour to models, while still keeping them clean.

States and transitions between them, are a frequent use case in large projects.

The state pattern treats "a state" as a first-class citizen of our codebase. Every state is represented by a separate class, and each of these classes acts upon a subject (model).

Write an abstract State class for the state, then subclass with all variations.

Transitions: a class which will take a model and change that model's state.

e.g. PendingToPaidTransition changes the invoice from Pending to Paid states.

Chapter 6: Managing Domains

https://stitcher.io/blog/laravel-beyond-crud-06-managing-domains

How to start using domains, to identify them, and to manage them long term?

Large projects not only have to manage the files and directory sizes, but also the massive amount of business logic.

It's about making large codebases easier to navigate and to keep the project healthier for longer.

It's healthy to keep iterating over your domain structure, to keep refactoring it.

A live doamin refactoring session (by Freek): https://www.youtube.com/watch?v=yPiMzw-lLF8

Chapter 7: Entering the Application Layer

https://stitcher.io/blog/laravel-beyond-crud-07-entering-the-application-layer

Connect the User to the Domain

One project can have several applications: HTTP, CLI. Also API, client/admin sides.

An application's goal is to take the user input, pass it to the domain and represent the output in a usable way for the user.

For large projects, rather than the usual Laravel app/ directory structure, this might be better (Invoices is one of many parts of the Admin domain)

App/Admin/Invoices
	├── Controllers
	├── Filters
	├── Middleware
	├── Queries
	├── Requests
	├── Resources
	└── ViewModels

Brent calls these Application Modules.

Chapter 8: View Models

TBA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment