Domain-specific operations

Similar to the previous rule, instead of creating setters to modify model properties or directly modifying public properties, encapsulate the logic in domain-specific methods. This way you can make sure that your models have a valid/consistent state at all times.

PHP
// GOOD

// Models\Member.php
public function confirmEmailAwaitingConfirmation(): void
{
    $this->email = $this->email_awaiting_confirmation;
    $this->email_awaiting_confirmation = null;
}

// Controllers\MemberEmailConfirmationController.php
public function store(): void
{
    $member = Auth::guard('member')->user();
    $member->confirmEmailWaitingConfirmation();
    $member->save();
}

// BAD =====================================

// Models\Member.php
public function setEmail(string $email): Member;
public function setEmailWaitingConfirmation(string $email): Member;

// Controllers\MemberEmailConfirmationController.php
public function store(): void
{
    $member = Auth::guard('member')->user();
    $member->email = $member->emailWaitingConfirmation;
    $member->emailWaitingConfirmation = null;
    $member->save();
}

Note that this convention aligns with the idea of putting domain logic into services and not into controllers or models, hence having rich domain services and thin controllers/models.

Want to learn more?

"Cruddy by Design" by Adam Wathan, 40 mins.
Read more about class invariants for a better understanding of the dangers of modifying class properties from controllers/services.