Clean code is the foundaton of a healthy app.

Governing Principles

Clear Code

One Thought Per Line

Each line of code should convey one thought. In PHP this is easiest eliminated by ensuring that each line of code only has one access operator (:: or ), or to the right of the assignment operator.

Chaining of access operators should be avoided through creation of attributes on models that encapsulate references to related objects. This keeps code more concise, optimizes maintainability by moving logic closer to its source.

One Idea Per Statement

Combining multiple thoughts creates an idea, thus each code statement should encapsulate a single idea, possibly across multiple lines.

Group Code By Concepts

Multiple ideas come together as a concept, by combining multiple statements into a group of statements separated by an empty line.

Encapsulate Each Concept in a Method

This can be further improved by refactoring each concept into its own method, and through careful naming can result in wonderfully readable, clean code.

Encapsulate Related Methods in a Class

In Laravel we often do not have this issue, as most business logic is contained within Models. However, sometimes we need to create classes to encapsulate concepts outside of models or other Laravel-prescribed functional classes. Action classes are optimal candidates for encapsulating isolated concepts.

Encapsulate Related Classes in a Domain

Taking this one step further, we can group related classes into a domain that represents functional blocks in the real world.

Domain-driven design takes this to the extreme:

Domain-driven design (less often, domain-driven design, DDD) is a set of principles and schemes aimed at creating optimal systems of objects. The development process boils down to creating software abstractions called domain models. These models include business logic that links the actual conditions of a product’s application to the code.

Naming

From Robert Martin's Clean Code:

Use Intention-Revealing Names

[…]

The name of a variable, function, or class, should answer all the big questions. It should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent.

[…]

Avoid Disinformation

[…]

Spelling similar concepts similarly is information. Using inconsistent spellings is dis-information. With modern [programming] environments we enjoy automatic code completion. We write a few characters of anime and press some hotkey combination (if that) and we are rewarded with a list of possible completions for that name. It is very helpful if names for very similar things sort together alphabetically and if the differences are very obvious, because the developer is likely to pick an object by name without seeing your copious comments or even the list of methods supplied by that class.

[…]

Use Pronounceable Names Humans are good at words. A significant part of our brains is dedicated to the concept of words. And words are, by definition, pronounceable. It would be a shame not to take advantage of that huge portion of our brains that has evolved to deal with spoken language. So make your names pronounceable.

[…]

Use Searchable Names Single-letter names and numeric constants have a particular problem in that they are not easy to locate across a body of text.

[…]

Avoid Mental Mapping Readers shouldn't have to mentally translate your names into other names they already know. This problem generally arises from a choice to use neither problem domain terms nor solution domain terms.

[…]

In general programmers are smart people. Smart people sometimes like to show off their smarts by demonstrating their mental juggling abilities. […]

One difference between a smart programmer and a professional programmer is that the professional understands that clarity is king. Professionals use their powers for good and write code that others can understand.

[…]

Pick One Word Per Concept Pick one word for one abstract concept and stick with it. For instance, it's confusing to have fetch, retrieve, and get as equivalent methods of different classes. How do you remember with method name goes with which class? […] Otherwise you spend an awful lot of time browsing through headers and previous code samples.

Casing

  • all variables, properties, and methods should be in camelCase.
  • all SQL fields should be in snake_case.
  • all SQL keywords should be in UPPERCASE.
  • all class names should be in PascalCase.
  • all urls, query strings, and config keys should be in snake_case.
  • all env vars should be in UPPER_SNAKE_CASE.

Takeaways:

  • Code should be self-documenting.
  • Code should clarify, and not obscure.
  • Code should have intention.
  • Code should be consistent, and adhere to expectations.

Boy Scout Rule

From Robert Martin's Clean Coder:

The Boy Scouts of America have a simple rule that we can apply to our profession:

"Leave the campground cleaner than you found it."

If we all checked in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanup doesn't have to be something big. Change one variable name for the better, break up one function that's a little too large, eliminate one small bit of duplication, clean up one composite if-statement.

Can you imagine working on a project where the code simply got a lot better as time passed? Do you believe that any other option is professional? Indeed, isn't continuous improvement an intrinsic part of professionalism?

Takeaways:

  • Improve each file you touch during the course of working your PR to continuously improve the project over time.

Debt

Technical Debt

From Wikipedia:

In software-intensive systems, technical debt is a collection of design or implementation constructs that are expedient in the short term, but set up a technical context that can make future changes more costly or impossible. Technical debt presents an actual or contingent liability whose impact is limited to internal system qualities, primarily maintainability and evolvability

From scrum.org:

There is also a kind of technical debt that is passively created when the Scrum Team learns more about the problem it is trying to solve. Today, the Development Team might prefer a different solution by comparison to the one the team implemented just six months ago. Or, the Development Team upgrades the definition of “Done,” thus introducing rework in former product Increments.

From Ward Cunningham:

Technical Debt is the deferment of good software design for the sake of expediency. Put simply, we’ve chosen to make some questionable design choices in order to get product delivered. This may be a conscious choice, but—more often than not—it’s unconscious, and the result of a time-crunch.

Takeaways:

  • address technical debt as soon as possible after it has been recognized
  • anyone on the team has the ability to identify technical debt

Mental Debt

Mental debt is the mental cost required to read code.

Takeaways:

  • write as few lines as possible: less code means having to parse less
  • carefully name classes, properties, and methods: clearly named objects convey their intended use clearly, and avoid making assumptions, or having to spend time figuring out what the name means
  • do not use abbreviations anywhere: abbreviations are not clear to everyone, do not assume others know the abbreviation, this helps keep code readable for new developers, or developers coming back to the code years from now, and does not assume knowledge of abbreviation conventions at the time the code was written
  • keep lines of code to under 100 characters: exceeding that break to a new line
  • unnecessary lines of code cause additional mental overhead in trying to figure out what they do, just to realize that they don't really do anything

No Dead Code

There should be no unused or commented code.

Takeaways:

  • This creates unnecessary code bloat, making the file harder to parse.
  • It raises questions as to why the code was commented, or included but not used. Code should answer questions, not raise them.

Don't Optimize Early

Optimizing without a specific need (code standards are already met, there are no apparent issues with the code) is pointless, and can make code worse.

Save optimization for the last possible moment, as any changes will inform how the code should be optimized.

From CodingDrills.com:

Increased Complexity: Premature optimization can introduce unnecessary complexity into the codebase. As developers focus on optimizing performance prematurely, they may end up sacrificing code readability, maintainability, and even correctness. The result is convoluted code that is difficult to debug and extend.

Time and Resource Waste: Optimizing code prematurely demands additional time and resources. Developers may spend hours or even days working on optimization, only to realize that the initial implementation was already performant enough. This wasted effort can slow down the development process, reducing productivity.

Compromised Code Quality: When developers prioritize optimization over clean code design and architecture, it can lead to compromises in overall code quality. The focus shifts from producing well-structured and maintainable code to achieving maximum performance. As a result, the codebase may become fragile, prone to bugs, and harder to evolve.

Takeaways:

  • follow the coding standards primarily
  • only optimize code when the need arises, as following the coding standards will already create highly maintainable code

Patterns

Model-View-Controller (MVC)

Model
  • This usually represents the core business logic. Models should have attributes that handle most of that, and any processing of the incoming request will take the model into account, either to persist or retrieve data.
View
  • This can be a model, response class, view, or value object.
Controller
  • Controller should contain no logic, instead it is only responsible for handling the incoming request, passing it to the corresponding Request Form class, then passing those results to the corresponding Response class (or view), which result is then return from the controller as the outgoing response.
  • Should always be a restful or __invoke for single action controllers

Don't Repeat Yourself (DRY)

  • Code should be abstracted/refactored out to small, manageable parts.
  • Unrelated code can then use common functionality that has been abstracted out of the other code path.
  • You shouldn't abstract out prematurely, only start abstracting out when other code needs to perform the same logic.

SOLID

Single Responsibility Principle

A class should have one and only one reason to change, meaning that a class should have only one job.

  • each class is responsible only for one thing:
    • Model defines its relationships, scopes, and attributes.
    • Controller handles how incoming request is handled (form request class) and how the outgoing response is prepared (model, view, response class, resource class)
    • Action is an invokable class with only one purpose.
Open-Closed Principle

Objects or entities should be open for extension but closed for modification.

  • Classes need to be extendable without requiring modification of the class itself.
Liskov Substitution Principle

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

  • Classes and their sub-classes should be able to be substituted without code breaking.
Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

  • If you add signatures to interfaces for functionality that does not apply to all classes that implement it, then you need to create a new interface, which will be implemented by the respective class in addition to the original interface.
Dependency Inversion Principle

Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.

When using Dependency Inversion, specify the interface instead of the concrete class. A great example is how Action classes are instantiated.

Repository

See Models reference.

Code Cleanliness

Conditionals

No Inline If-Statements

  • do not use inline if-statements.

Why

  • makes code easier to parse (reduces mental debt)

Avoid Conditionals

Avoid conditionals where possible.

Why

  • they increase cyclomatic complexity, which increases mental debt.

No else or elseif

If you have to use if-statements in PHP, never use else or elseif.

Why

  • they are unnecessary lines of code that can be achieved with fewer lines of code in simpler terms (reduces mental debt)
  • reduces code complexity (reduces mental debt)

One Condition Per Line

  •  if there is only a single condition in an if-statement, keep the if-statement condition portion on a single line, do not place the condition on its own line.
  • for conditions that consist of multiple conditions, place each condition on its own line with the operator preceding the condition

Why

  • makes code easier to parse (reduces mental debt)

Ternary Conditionals

  • use ternary operators instead of if statements where possible
  • do not nest ternary conditions, instead assign to variables or refactor to methods

Why

  • makes code easier to parse (reduces mental debt)

Combine Where Possible

  • combine sequential conditions that have the same result

Why

  • reduces code complexity (reduces mental debt)

Mapping Arrays

  • use mapping arrays instead of multiple if-statements when inspecting different values of the same variable

Why

  • reduces code complexity (reduces mental debt)

Strings

  • use interpolation in favor of concatenation
  • HTML attributes should always use quotes, never apostrophes
  • defining HTML or other code to be rendered within code should be done using HereDocs.
  • escape quotes when rendering inside other quotes

Why

  • strings quoted in the same way can be sorted.
  • Reduced mental load when reading code that has nested quotes.
  • Adherence to HTML5 standards.

Collections

Only Use Collection Methods

  • don't use generic PHP methods on collections, collections should be implemented for the built-in methods, as they are optimized, and decouple us from direct PHP implementation

Why

  • decoupling from PHP methods (technical debt)
  • optimized functionality (performance)
  • consistent usage (mental debt)

Arrays

Convert To Collection

  • whenever possible use collections for manipulation

Why

  • collections provide a large list of optimized manipulation methods
  • by relying on the framework, we are decoupling from the direct PHP implementation, which may see changes over major versions, while the framework will maintain optimized implementation

Array Accessors

Always use data_get() to access arrays, instead of accessing their elements directly.

Why

  • this provides fallback logic in case the element does not exist in arrays
  • allows parsing of properties on any kind of object (array, collection, object, model), thus not requiring type checks (reduces mental debt)
  • allows easy parsing of nested objects (reduces mental debt)

Operators

  • all operators should be surrounded by 1 space, with the exception of operators at the beginning of the statement, such as the notoperator, in which case it does not have a preceding space:
if (! $test) {
    // ...
}
  • operators to the right of the assignment operator should start a new line
  • don't line break after comparison or assignment operators

Why

  • makes code easier to parse (reduces mental load)

Indentation

Methods

Nesting code often indicates different concepts or concerns, and are indication that code should be refactored. Methods should have no more than 2 levels of nesting.

Why

  • reduces code complexity (reduces mental debt)
  • separation of concerns (group code by concepts)

Multi-Line Statements

If a single statement extends over multiple lines, any lines subsequent to the first should be indented by one level.

Why

  • indicates coherence between lines of code (mental debt)

Logical Groupings

If-statements that have complex logic should have one condition per line, and groupings of conditions (using parentheses) should indent subsequent conditions to the first within the parentheses.

Why

  • indicates the logical structure of conditions, so they can be quickly parsed (mental debt)

Blank Lines

  • should only be used to separate concepts
  • at most there should be a single blank line, never multiple
  • there should be no blank lines at the beginning or end of classes, methods, or functions

Why

  • blank lines have meaning, and should only be used where appropriate to provide consistency and improve parsing of code (mental debt, clear code)

Line Length

Lines of code should be no longer than 100 characters, and may absolutely be no longer than 120 characters.

Why

  • makes code easier to parse (reduces mental debt, one thought per line)

Type Hints and Return Types

  • Type hint all method parameters and return values.
  • Type hints serve as documentation, making the code more fluent and readable.
  • Type hints and return types prevent some logic errors from propagating, catching them as close as possible to their source.

Why

  • catches logic issues related to unexpected data changes close to the source
  • makes code easier to understand (reduces mental debt)

Code Style

Industry Standards

All code style must adhere to the following PHP standards:

  • PSR1
  • PSR2
  • PSR12

Linters

Your editor must support PHPCS listing, and be configured to use the phpcs.xml file in the root of the project. Your editor will then alert you to any style violation that we have linters for (which won't be all). Violations not covered by linters should be attempted to be caught and fixed during review.

We should not use any sort of auto-formatter that corrects linter issues, as this blows out the reviews and hides the actual changes made. Further, manual correction reinforced good coding habits, and after a short time you will be able to write mostly clean code with ease.

Strings

Multiline Strings

All multiline strings should use HEREDOC syntax:

$string = <<<HTML
    <div>
        Hello, world!
    </div>
HTML;

This is especially important for inline SQL statements:

$sql = DB::statement(<<<SQL
    SELECT "Hello, world!"
SQL);

Operators

Evaluative

These operators should never have a new line to the right or left:

if ("hello" === "world) {
    //
}
  • comparison: ==, ===, !=, <>, !==, <, >, <=, >=, <=>
  • type: instanceof

Manipulative

Manipulation operators should start on a new line in standalone statements, but may be written in a single line if acting as parameters:

$result = 4
    + 4;
$result = floor(4 + 4.1);
$string = "Hello"
    . Str::lower(", world!");

if (
    $isTrue
    && $isAlsoTrue
) {
    //
}

The following are the most common manipulation operators:

  • strings: .
  • math: +, -, /, *, %, **
  • logical: &&, ||
  • bitwise: &, |, ^, ~, <<, >>

Active

These operators should have a space between themselves and the object they are acting on:

  • arithmetic assignment: =, +=, -=, /=, *=, %=, **=
  • bitwise assignment: &=, |=, ^=, <<=, >>=
  • other assignment: .=, ??=
  • logical: and, or, xor, !, &&, ||
  • string: .

Passive

These operators should have no space between themselves and the object they are acting on:

  • identity: +
  • negation: -
  • increment: ++
  • decrement: --
  • error control: @
  • execution: ````
  • access: [], ->

Classes

Use Statements

Sort Alphabetically

  • should be ordered alphabetically.

Why: easier to parse, especially with many entries (mental debt).

No Unused Entries

  • should not include unused entries.
Why

less code is the best code (mental debt)

unused code is useless (no dead code)

Contracts (Interfaces)

Contracts aim to loosen coupling of objects in an application. It is important to remember that coupling is shifted from concrete implementations to abstract contracts (which have no logic, but only specify method interfaces).

However, contracts are not always needed, and should be used only where actually useful, like instantiation of objects through dependency injection, or code used by others, especially packages. This lets us easily switch out implementations, especially in third-party-packages that might require some customization.

Why

  • provides loose coupling (SOLID)

No Statics

Avoid static classes. Classes are intended to be instantiated and be identifiable. Static classes do not have an identity are are not true objects, thus are a stow-away from the procedural era.

Further, static classes and methods are little more than modern equivalents of GOTO statements, procedural in nature. OOP goes beyond that, the object is the defining principle, not the code. Static classes and methods are a crutch used to think procedurally under the guise of seeming object-oriented.

Why

  • there is virtually no overhead in instantiating a class (pointless optimization)
  • classes can be expected to behave in the same manner (mental debt)
  • objects are inherently easier to test and inspect than static classes (testability)

Class Naming

All classes should be suffixed based on the base folder within the app folder, with exception of the following:

  • app/Models: no suffix
  • app/Http: file suffix is based on the folder within app/Http
  • app/Livewire: no suffix
  • app/Livewire/Forms: Form suffix, only Livewire Form classes here

Constructors: Primary + Named Constructors

Use one primary constructor, along with multiple secondary (named) constructors that all make use of the primary constructor.

Why
  • accommodate different scenarios without repeating code (DRY)

No Logic

  • Constructors should not include any functionality or logic, but merely assign values to object properties. If logic needs to be performed, this is an indication that the information passed in should actually be another object. The reason behind this is that any code in the constructor will be parsed every time an object is created, regardless if it is necessary or not. That can't be optimized. Instead of only assignments are handled in constructors, optimization can be controlled, and only the necessary code performed.
Why
  • Prevents optimization (technical debt)

Property Promotion

  • Use property promotion in constructors, avoid defining class properties outside of the constructor.
Why
  • reduces lines of code (mental debt)
  • defines the parameters where they are introduced, makes it easier to parse code (mental debt)

Properties

Are Required

Avoid classes that do not encapsulate any data. Classes without properties have no state and no identity, and are analogous to procedural, non-object-oriented code.

Why
  • Classes represent concepts, all concepts have properties, this is an expectation that we have of things. (mental debt)

Methods

Naming

From Robert Martin's Clean Code:

Method Names Methods should have a verb or verb-phrase names like postPayment, deletePage, or save.

  • Name methods according to what they do or return. Their names should be self-documenting.
  • Methods that perform an action should be a verb, and not return anything.
  • Methods that return objects should be nouns and named after the object they return (they can be prefixed with adjectives that help better describe the object being returned). In Models this should always be attributes.
  • methods should read as an action being taken on the class
Why
  • set expectations as to what they do (mental debt, naming)
  • methods transform a class instance, which is indicated by the action taken, which is expressed using verbs or verb-phrases (naming)

Declared Parameters

Methods should have a declared parameter list, and not use a dynamic one (exceptions are magic methods).

Why
  • Helps maintainability and readability (mental debt)

Type Hints

Methods should have type-hinted parameters as well a a return type.

Why
  • helps maintainability and readability, as well as is self-documenting (mental debt)
  • traps logic errors close to the source (technical debt)

No Null Arguments

When calling methods with optional parameters, don't pass null into the methods, use named parameters instead.

Why
  • helps maintainability and readability (mental debt)
  • reduces code (no dead code)

Dependency Injection

Where possible, classes should be injected via the constructor, allowing resolution through Inversion of Control (IoC) and avoiding tight coupling between classes.

Why

  • provides loose coupling between classes (SOLID)
  • creates easier-to-maintain code as we can decide which instance to provide when the class is instantiated (tech debt)
  • allows automatic resolution through Inversion of Control if we don't provide an instance (SOLID)

Introspection / Type Casting

Avoid introspection (checking the type of the class to determine the outcome of a condition), for example using methods like instanceOf. This creates tight coupling, and introduces technical debt, as the object type should already be defined in the method parameter or class property. If you have loosely coupled code, but use introspection, you are introducing another point of failure: what happens when the item you are inspecting is not of the expected type, but adheres to the correct interface?

If you find that you are reaching for introspection, it probably means that logic should be encapsulated or refactored, likely resulting in rearranging or creating classes.

Why

  • causes brittle code (tech debt)

Models

Eager Loading

  • Avoid eager loading relationships in the protected $with = []; variable as this could lead to data bloat.
  • Try to explicitly load relationships at the point they are used using the with() method on the eloquent query, instead of using the load() method later in the code.

Organization

  1. List traits, in alphabetical order, only one trait per line.
  2. List the public, protected, and private properties, each group in alphabetical order.
  3. List relationship methods in alphabetical order.
  4. List getter and setter methods in alphabetical order.
  5. List any other methods in alphabetical order.

Persistence Methods (Repository Pattern)

  • Laravel models are the de-facto persistence repository, especially in eloquent. Do not create repository classes.
  • Do not use the generic eloquent CRUD methods save()update()create()delete(), etc. outside of the model. Instead create descriptive methods that explain exactly what is happening. This decouples the business domain from the persistence domain of the app, as well as makes code so much more humanly readable. For instance: instead of $user->save() create a method that handles a specific situation, like $agent->addListingInfo($listingInfo); and then handle all the data parsing and assignment in the method, at the end of which $this->save() is called to persist the changes.
  • This pattern is not a Repository pattern, but instead an adaptation thereof for Laravel models. Laravel models already implement the repository patter in how they are build on top of Eloquent, so splitting out each model into multiple single-use-traits is an effort to maintain organization, while the other rules enforce the repository pattern of the model throughout the code-base.
  • The benefits of this guide go deep beneath the surface, and at first glance may not be apparent:
    • centralized place of maintenance in the attribute and query traits.
    • deep performance optimization via centralized caching in the traits. This automatically ensures that relationship references are cached as well, for example when looping over a relationship collection.
    • reduced technical dept, as queries and custom attributes are all contained in known centralized traits. This makes maintenance much easier when fixing or optimizing queries, as it is no longer necessary to inspect your entire codebase.
    • reduced visual dept in models, as the custom parts are separated out into traits, keeping the model itself lean and to the point.
    • adoption of better patterns: separating and centralizing queries helps DRY up your code, as well as force you to think more about how each query works and discover areas it can be optimized, by pulling it out of context, and looking at it on its own, without being distracted by the domain logic surrounding it.

Structure

  • All attribute methods should be extracted to an Attributes trait.
  • All query methods should be extracted to traits:
App
|-Concerns
| |-Attributes
| | \-Book
| |
| \-Queries
|   \-Book
|
|-BaseModel
\-Book
<?php

declare(strict_types=1);

namespace App;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

abstract class BaseModel extends Model
{
    use Searchable;
}
<?php

declare(strict_types=1);

namespace App;

use App\Traits\Attributes\Contact as Attributes;
use App\Traits\Queries\Contact as Queries;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Contact extends BaseModel
{
    use Attributes;
    use Queries;

    protected $appends = [
        'searchKey',
        'searchUrl',
    ];
    protected $fillable = [
        'name',
        'title',
        'work_phone',
        'mobile_phone',
        'work_email',
        'private_email',
    ];

    public function contactTypes() : BelongsToMany
    {
        return $this->belongsToMany(ContactType::class);
    }
}
<?php

declare(strict_types=1);

namespace App\Traits\Attributes;

trait Contact
{
    public function getGravatarAttribute() : string
    {
        $defaultAvatar = 'https://www.gravatar.com/avatar/?d=mm';
        $workAvatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($this->work_email))) . '?d=mm';
        $privateAvatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($this->private_email))) . '?d=mm';

        if (md5(file_get_contents($workAvatar)) !== md5(file_get_contents($defaultAvatar))) {
            return $workAvatar;
        }

        if (md5(file_get_contents($privateAvatar)) !== md5(file_get_contents($defaultAvatar))) {
            return $privateAvatar;
        }

        return $defaultAvatar;
    }

    public function getSearchKeyAttribute() : string
    {
        return $this->name;
    }

    public function getSearchUrlAttribute() : string
    {
        return route('contacts.show', $this->getKey());
    }
}
<?php namespace App\Traits\Queries;

use Illuminate\Support\Collection;

trait Contact
{
    public function getAll() : Collection
    {
        return cache()->tags([$this->classSlug])
            ->rememberForever("contact-{$this->id}-getAll", function () {
                return $this->orderBy('name')->get();
            });
    }

    public function getByTypes(array $types) : Collection
    {
        $key = implode('-', $types);

        return cache()->tags([$this->classSlug, 'contact-type'])
            ->rememberForever("contact-{$this->id}-getByTypes-{$key}", function () use ($types) {
                return $this->with(['contactTypes' => function ($query) use ($types) {
                        $query->whereIn('title', $types);
                    }])
                    ->orderBy('name')
                    ->get();
            });
    }
}

Relationship Properties

  • Do not query relationship properties on models directly. Instead expose the relationship property as an attribute of the model itself. This allows you to provide a default if the relationship does not exist, as well as limits interdependence of models to only the models themselves, and not through your code. For example, instead of $book->author->name, create a model attribute called authorName, then call that as $book->authorName in your code:
<?php

declare(strict_types=1);

namespace App;

use App\Concerns\Attributes\Book as Attributes;

class Book extends Model
{
    use Attributes;

    public function author() : HasOne
    {
        return $this->hasOne(Author::class);
    }
}

<?php

declare(strict_types=1);

namespace App\Concerns\Attributes;

trait Book
{
    public function getAuthorNameAttribute(): string
    {
        return $this->author->name
            ?? "";
    }
}

Naming Conventions

Properties or Methods with Certain Return Types

  • boolean properties should start with is, has, should, etc, indicating a question that can be answered as yes or no.
  • boolean properties or methods that check if a certain condition is true should be named has<Condition in past tense>.

Query Methods

  • methods returning a single instance should be prefixed with find followed by the name of the model instance it returns, for example ->findUserByName(string $name).
  • methods returning a collection of instances should be prefixed with get followed by the name of the model instances is returns, for example ->getUsersByType(string $type).

Attributes

Livewire

Components

  • Each Livewire component must have a single root element (usually a div) with no Livewire, Blade, or Alpine attributes.
  • Add a unique wire:key to each component:
<mycomponent
    wire:key="my-key"
/>
  • Livewire components that are in loops or when multiple components are adjacent, wrap each component in a <template> tag that has the same wire:key attribute as the component:
<template
    wire:key="my-unique-key"
>
    <mycomponent
        wire:key="my-key"
    />
</template>

Controllers

No Business Logic

  • Controllers should only control the flow of requests and responses. All business logic should be extracted to Form Request classes and Response classes, leaving the controller with only a few lines of code within each method.
  • Controllers should be either restful or invokable, no custom actions. If you are reaching for custom actions, that is a code smell that the controller or related model has not been named or extracted granularly enough.

Route Model Binding

  • Controllers should auto-resolve models through route-model-binding, by adding the model parameter to the method, even if it is not used in the method, as simply adding it triggers the binding, and it is then available to be accessed in the Form Request class.
  • Route-model binding can be customized in the RouteServiceProvider as needed.

Policies

Secure Front- and Back-Ends

  • Checks should be implemented on the frontend to prevent displaying of unwanted elements.
  • Checks should be implemented on the backend to prevent execution of unwanted code, in the event front-end restrictions are being circumvented.

Testing

Guidelines

  • When starting an app, start where you would start with writing code. The first test does not have to be elegant, or even correct. The most important thing is just to get started.
  • Goal of tests is to get as quickly as possible to "Shameless Green", which means that no matter how ugly your code is, it satisfies all tests, thus is "green".
  • One of the principles of Shameless Green is that code is written for understanding, rather than extreme adherence to any and all patterns. The human is the focus.
  • Always write unit and integration tests, testing for success and failure for each scenario.
  • Only test public methods of classes.
  • Write tests so that they cover all protected and private methods of the class, accessed through the public methods. If there are non-public methods that are not covered, they are either inaccessible, or the tests are not comprehensive enough. If they are inaccessible, those methods should be removed.
  • Tests should document the functionality of classes and their methods through careful naming of test methods.
  • Mock any external interfaces you do not control, and test for both successes and failures. However, also create integration tests that test the external interface, so that the mocks do not become stale if the external interface changes.
  • Do not mock classes that you control.

Development Process

  • Write unite tests before implementing classes (only implement classes, never procedural code).
  • Always do Red/Green/Refactor TDD. This means writing tests for the code you would like to see in an optimal world. Then make the failing test pass using the minimum amount of code. Write another test to expand on the first test, which again makes the existing code fail. Refactor your code to make the second test pass. Rinse and repeat until you have the minimum necessary functionality for your MVP (minimum viable product).
  • During the red/green/refactor process, keep in mind that you need to develop from two different perspectives:
    1. When writing tests, keep the larger picture of the application and business domain in mind.
    2. When writing code to satisfy tests, only think about the test that needs to be satisfied. DO NOT THINK ABOUT BUSINESS LOGIC, ONLY FOCUS ON MAKING TESTS GREEN.
  • As your tests get more specific, your code should become more generic. Consider Robert Martin's Transformation Priority Premise (https://8thlight.com/blog/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) when writing functional code to keep code complexity at a minimum. Try to opt for the highest ranked option.
  • What this means is that you start out satisfying the tests with minimal or no logic, and as your tests become more specific to certain use cases, the code gains more business logic and is more generically applicable.
  • Never add code that wont be used.
  • Remove any code that is not used.
  • Use cyclomatic complexity as a guide for the number of tests needed to achieve full test coverage of your code (perhaps 1 test per complexity unit).
  • Wait to DRY out duplicated code until after a few tests cover it. This way the correct abstraction might reveal itself, rather than prematurely abstracting it out incorrectly.

Databases

Do not use SQLite for testing if:

  • you are using JSON fields
  • require exact float value calculations based on decimal fields
  • have table alterations in your migrations
  • if you have raw queries which manipulate dates

Test Suites

Unit Tests

Unit tests are tests that concern themselves only with the class under test. These are rare in Laravel, as most classes in Laravel have external concerns (controllers, models, listeners, events, jobs, etc.).

Even though, we should strive to write unit tests if at all possible, as they are the fastest.

Feature Tests

Feature tests are any tests that use more than internal methods of a class, for instance use the database, other classes, HTTP requests, WebSockets, etc., but which do NOT traverse the internet to connect with external functionality.

Third-party APIs should be tested in feature tests through use of HTTP fakes. When doing so, an identical test should be written as an integration test that does not use fakes.

Integration Tests

These are tests dedicated to requests that test external dependencies over the internet.

Exceptions

  • Catch errors and exceptions as soon as possible. Use type hinting and return types as one aspect toward achieving this.
  • Create custom exceptions as much as possible, especially when catching exceptions for special handling.
  • Always use Throwable for type-hinting exceptions, most often when catching.
  • If the $exception is not used in the try-catch block, simply type hint like so:
try {
    //
} catch (Throwable) {
    //
}

Routes

✅ Do

  • always use resource routes that point to restful controllers
  • for special action routes, use invokable controllers, this should be very rare
  • only associate routes with a single model
  • name the controller and route after the model they act on

⛔️ Do Not

  • use closures in routes, as they cannot be cached in php artisan route:cache
  • create routes that do not relate to models

Types

API

  • API routes should be within an API route namespace.

View

  • should have no namespace/prefix (as the API routes do)
  • controllers should only be responsible for a single model, but responsible for all views that pertain to that model, for example:
/resources/views/reports/index.blade.php
/resources/views/reports/create.blade.php
/resources/views/reports/show.blade.php